Repository: CodeAcmen/ios-async-socket-explorer
Branch: master
Commit: c385c9c091c5
Files: 1153
Total size: 4.8 MB
Directory structure:
gitextract_kmpt9gt3/
├── LICENSE
├── Podfile
├── Pods/
│ ├── CocoaAsyncSocket/
│ │ ├── LICENSE.txt
│ │ ├── README.markdown
│ │ └── Source/
│ │ └── GCD/
│ │ ├── GCDAsyncSocket.h
│ │ ├── GCDAsyncSocket.m
│ │ ├── GCDAsyncUdpSocket.h
│ │ └── GCDAsyncUdpSocket.m
│ ├── DZNEmptyDataSet/
│ │ ├── LICENSE
│ │ ├── README.md
│ │ └── Source/
│ │ ├── UIScrollView+EmptyDataSet.h
│ │ └── UIScrollView+EmptyDataSet.m
│ ├── Masonry/
│ │ ├── LICENSE
│ │ ├── Masonry/
│ │ │ ├── MASCompositeConstraint.h
│ │ │ ├── MASCompositeConstraint.m
│ │ │ ├── MASConstraint+Private.h
│ │ │ ├── MASConstraint.h
│ │ │ ├── MASConstraint.m
│ │ │ ├── MASConstraintMaker.h
│ │ │ ├── MASConstraintMaker.m
│ │ │ ├── MASLayoutConstraint.h
│ │ │ ├── MASLayoutConstraint.m
│ │ │ ├── MASUtilities.h
│ │ │ ├── MASViewAttribute.h
│ │ │ ├── MASViewAttribute.m
│ │ │ ├── MASViewConstraint.h
│ │ │ ├── MASViewConstraint.m
│ │ │ ├── Masonry.h
│ │ │ ├── NSArray+MASAdditions.h
│ │ │ ├── NSArray+MASAdditions.m
│ │ │ ├── NSArray+MASShorthandAdditions.h
│ │ │ ├── NSLayoutConstraint+MASDebugAdditions.h
│ │ │ ├── NSLayoutConstraint+MASDebugAdditions.m
│ │ │ ├── View+MASAdditions.h
│ │ │ ├── View+MASAdditions.m
│ │ │ ├── View+MASShorthandAdditions.h
│ │ │ ├── ViewController+MASAdditions.h
│ │ │ └── ViewController+MASAdditions.m
│ │ └── README.md
│ ├── OCMock/
│ │ ├── License.txt
│ │ ├── README.md
│ │ └── Source/
│ │ └── OCMock/
│ │ ├── NSInvocation+OCMAdditions.h
│ │ ├── NSInvocation+OCMAdditions.m
│ │ ├── NSMethodSignature+OCMAdditions.h
│ │ ├── NSMethodSignature+OCMAdditions.m
│ │ ├── NSNotificationCenter+OCMAdditions.h
│ │ ├── NSNotificationCenter+OCMAdditions.m
│ │ ├── NSObject+OCMAdditions.h
│ │ ├── NSObject+OCMAdditions.m
│ │ ├── NSValue+OCMAdditions.h
│ │ ├── NSValue+OCMAdditions.m
│ │ ├── OCClassMockObject.h
│ │ ├── OCClassMockObject.m
│ │ ├── OCMArg.h
│ │ ├── OCMArg.m
│ │ ├── OCMArgAction.h
│ │ ├── OCMArgAction.m
│ │ ├── OCMBlockArgCaller.h
│ │ ├── OCMBlockArgCaller.m
│ │ ├── OCMBlockCaller.h
│ │ ├── OCMBlockCaller.m
│ │ ├── OCMBoxedReturnValueProvider.h
│ │ ├── OCMBoxedReturnValueProvider.m
│ │ ├── OCMConstraint.h
│ │ ├── OCMConstraint.m
│ │ ├── OCMExceptionReturnValueProvider.h
│ │ ├── OCMExceptionReturnValueProvider.m
│ │ ├── OCMExpectationRecorder.h
│ │ ├── OCMExpectationRecorder.m
│ │ ├── OCMFunctions.h
│ │ ├── OCMFunctions.m
│ │ ├── OCMFunctionsPrivate.h
│ │ ├── OCMIndirectReturnValueProvider.h
│ │ ├── OCMIndirectReturnValueProvider.m
│ │ ├── OCMInvocationExpectation.h
│ │ ├── OCMInvocationExpectation.m
│ │ ├── OCMInvocationMatcher.h
│ │ ├── OCMInvocationMatcher.m
│ │ ├── OCMInvocationStub.h
│ │ ├── OCMInvocationStub.m
│ │ ├── OCMLocation.h
│ │ ├── OCMLocation.m
│ │ ├── OCMMacroState.h
│ │ ├── OCMMacroState.m
│ │ ├── OCMNonRetainingObjectReturnValueProvider.h
│ │ ├── OCMNonRetainingObjectReturnValueProvider.m
│ │ ├── OCMNotificationPoster.h
│ │ ├── OCMNotificationPoster.m
│ │ ├── OCMObjectReturnValueProvider.h
│ │ ├── OCMObjectReturnValueProvider.m
│ │ ├── OCMObserverRecorder.h
│ │ ├── OCMObserverRecorder.m
│ │ ├── OCMPassByRefSetter.h
│ │ ├── OCMPassByRefSetter.m
│ │ ├── OCMQuantifier.h
│ │ ├── OCMQuantifier.m
│ │ ├── OCMRealObjectForwarder.h
│ │ ├── OCMRealObjectForwarder.m
│ │ ├── OCMRecorder.h
│ │ ├── OCMRecorder.m
│ │ ├── OCMStubRecorder.h
│ │ ├── OCMStubRecorder.m
│ │ ├── OCMVerifier.h
│ │ ├── OCMVerifier.m
│ │ ├── OCMock.h
│ │ ├── OCMockMacros.h
│ │ ├── OCMockObject.h
│ │ ├── OCMockObject.m
│ │ ├── OCObserverMockObject.h
│ │ ├── OCObserverMockObject.m
│ │ ├── OCPartialMockObject.h
│ │ ├── OCPartialMockObject.m
│ │ ├── OCProtocolMockObject.h
│ │ └── OCProtocolMockObject.m
│ ├── Pods.xcodeproj/
│ │ ├── project.pbxproj
│ │ └── xcuserdata/
│ │ └── aarongtang.xcuserdatad/
│ │ └── xcschemes/
│ │ ├── CocoaAsyncSocket.xcscheme
│ │ ├── DZNEmptyDataSet.xcscheme
│ │ ├── Masonry.xcscheme
│ │ ├── OCMock.xcscheme
│ │ ├── Pods-iOS-Network-Stack-Dive.xcscheme
│ │ ├── Pods-iOS-Network-Stack-DiveTests.xcscheme
│ │ ├── Reachability.xcscheme
│ │ ├── ReactiveObjC.xcscheme
│ │ ├── SDWebImage-SDWebImage.xcscheme
│ │ ├── SDWebImage.xcscheme
│ │ ├── Typhoon.xcscheme
│ │ ├── YYModel.xcscheme
│ │ └── xcschememanagement.plist
│ ├── Reachability/
│ │ ├── LICENCE.txt
│ │ ├── README.md
│ │ ├── Reachability.h
│ │ └── Reachability.m
│ ├── ReactiveObjC/
│ │ ├── LICENSE.md
│ │ ├── README.md
│ │ └── ReactiveObjC/
│ │ ├── MKAnnotationView+RACSignalSupport.h
│ │ ├── MKAnnotationView+RACSignalSupport.m
│ │ ├── NSArray+RACSequenceAdditions.h
│ │ ├── NSArray+RACSequenceAdditions.m
│ │ ├── NSData+RACSupport.h
│ │ ├── NSData+RACSupport.m
│ │ ├── NSDictionary+RACSequenceAdditions.h
│ │ ├── NSDictionary+RACSequenceAdditions.m
│ │ ├── NSEnumerator+RACSequenceAdditions.h
│ │ ├── NSEnumerator+RACSequenceAdditions.m
│ │ ├── NSFileHandle+RACSupport.h
│ │ ├── NSFileHandle+RACSupport.m
│ │ ├── NSIndexSet+RACSequenceAdditions.h
│ │ ├── NSIndexSet+RACSequenceAdditions.m
│ │ ├── NSInvocation+RACTypeParsing.h
│ │ ├── NSInvocation+RACTypeParsing.m
│ │ ├── NSNotificationCenter+RACSupport.h
│ │ ├── NSNotificationCenter+RACSupport.m
│ │ ├── NSObject+RACDeallocating.h
│ │ ├── NSObject+RACDeallocating.m
│ │ ├── NSObject+RACDescription.h
│ │ ├── NSObject+RACDescription.m
│ │ ├── NSObject+RACKVOWrapper.h
│ │ ├── NSObject+RACKVOWrapper.m
│ │ ├── NSObject+RACLifting.h
│ │ ├── NSObject+RACLifting.m
│ │ ├── NSObject+RACPropertySubscribing.h
│ │ ├── NSObject+RACPropertySubscribing.m
│ │ ├── NSObject+RACSelectorSignal.h
│ │ ├── NSObject+RACSelectorSignal.m
│ │ ├── NSOrderedSet+RACSequenceAdditions.h
│ │ ├── NSOrderedSet+RACSequenceAdditions.m
│ │ ├── NSSet+RACSequenceAdditions.h
│ │ ├── NSSet+RACSequenceAdditions.m
│ │ ├── NSString+RACKeyPathUtilities.h
│ │ ├── NSString+RACKeyPathUtilities.m
│ │ ├── NSString+RACSequenceAdditions.h
│ │ ├── NSString+RACSequenceAdditions.m
│ │ ├── NSString+RACSupport.h
│ │ ├── NSString+RACSupport.m
│ │ ├── NSURLConnection+RACSupport.h
│ │ ├── NSURLConnection+RACSupport.m
│ │ ├── NSUserDefaults+RACSupport.h
│ │ ├── NSUserDefaults+RACSupport.m
│ │ ├── RACAnnotations.h
│ │ ├── RACArraySequence.h
│ │ ├── RACArraySequence.m
│ │ ├── RACBehaviorSubject.h
│ │ ├── RACBehaviorSubject.m
│ │ ├── RACBlockTrampoline.h
│ │ ├── RACBlockTrampoline.m
│ │ ├── RACChannel.h
│ │ ├── RACChannel.m
│ │ ├── RACCommand.h
│ │ ├── RACCommand.m
│ │ ├── RACCompoundDisposable.h
│ │ ├── RACCompoundDisposable.m
│ │ ├── RACCompoundDisposableProvider.d
│ │ ├── RACDelegateProxy.h
│ │ ├── RACDelegateProxy.m
│ │ ├── RACDisposable.h
│ │ ├── RACDisposable.m
│ │ ├── RACDynamicSequence.h
│ │ ├── RACDynamicSequence.m
│ │ ├── RACDynamicSignal.h
│ │ ├── RACDynamicSignal.m
│ │ ├── RACEagerSequence.h
│ │ ├── RACEagerSequence.m
│ │ ├── RACEmptySequence.h
│ │ ├── RACEmptySequence.m
│ │ ├── RACEmptySignal.h
│ │ ├── RACEmptySignal.m
│ │ ├── RACErrorSignal.h
│ │ ├── RACErrorSignal.m
│ │ ├── RACEvent.h
│ │ ├── RACEvent.m
│ │ ├── RACGroupedSignal.h
│ │ ├── RACGroupedSignal.m
│ │ ├── RACImmediateScheduler.h
│ │ ├── RACImmediateScheduler.m
│ │ ├── RACIndexSetSequence.h
│ │ ├── RACIndexSetSequence.m
│ │ ├── RACKVOChannel.h
│ │ ├── RACKVOChannel.m
│ │ ├── RACKVOProxy.h
│ │ ├── RACKVOProxy.m
│ │ ├── RACKVOTrampoline.h
│ │ ├── RACKVOTrampoline.m
│ │ ├── RACMulticastConnection+Private.h
│ │ ├── RACMulticastConnection.h
│ │ ├── RACMulticastConnection.m
│ │ ├── RACPassthroughSubscriber.h
│ │ ├── RACPassthroughSubscriber.m
│ │ ├── RACQueueScheduler+Subclass.h
│ │ ├── RACQueueScheduler.h
│ │ ├── RACQueueScheduler.m
│ │ ├── RACReplaySubject.h
│ │ ├── RACReplaySubject.m
│ │ ├── RACReturnSignal.h
│ │ ├── RACReturnSignal.m
│ │ ├── RACScheduler+Private.h
│ │ ├── RACScheduler+Subclass.h
│ │ ├── RACScheduler.h
│ │ ├── RACScheduler.m
│ │ ├── RACScopedDisposable.h
│ │ ├── RACScopedDisposable.m
│ │ ├── RACSequence.h
│ │ ├── RACSequence.m
│ │ ├── RACSerialDisposable.h
│ │ ├── RACSerialDisposable.m
│ │ ├── RACSignal+Operations.h
│ │ ├── RACSignal+Operations.m
│ │ ├── RACSignal.h
│ │ ├── RACSignal.m
│ │ ├── RACSignalProvider.d
│ │ ├── RACSignalSequence.h
│ │ ├── RACSignalSequence.m
│ │ ├── RACStream+Private.h
│ │ ├── RACStream.h
│ │ ├── RACStream.m
│ │ ├── RACStringSequence.h
│ │ ├── RACStringSequence.m
│ │ ├── RACSubject.h
│ │ ├── RACSubject.m
│ │ ├── RACSubscriber+Private.h
│ │ ├── RACSubscriber.h
│ │ ├── RACSubscriber.m
│ │ ├── RACSubscriptingAssignmentTrampoline.h
│ │ ├── RACSubscriptingAssignmentTrampoline.m
│ │ ├── RACSubscriptionScheduler.h
│ │ ├── RACSubscriptionScheduler.m
│ │ ├── RACTargetQueueScheduler.h
│ │ ├── RACTargetQueueScheduler.m
│ │ ├── RACTestScheduler.h
│ │ ├── RACTestScheduler.m
│ │ ├── RACTuple.h
│ │ ├── RACTuple.m
│ │ ├── RACTupleSequence.h
│ │ ├── RACTupleSequence.m
│ │ ├── RACUnarySequence.h
│ │ ├── RACUnarySequence.m
│ │ ├── RACUnit.h
│ │ ├── RACUnit.m
│ │ ├── RACValueTransformer.h
│ │ ├── RACValueTransformer.m
│ │ ├── ReactiveObjC.h
│ │ ├── UIActionSheet+RACSignalSupport.h
│ │ ├── UIActionSheet+RACSignalSupport.m
│ │ ├── UIAlertView+RACSignalSupport.h
│ │ ├── UIAlertView+RACSignalSupport.m
│ │ ├── UIBarButtonItem+RACCommandSupport.h
│ │ ├── UIBarButtonItem+RACCommandSupport.m
│ │ ├── UIButton+RACCommandSupport.h
│ │ ├── UIButton+RACCommandSupport.m
│ │ ├── UICollectionReusableView+RACSignalSupport.h
│ │ ├── UICollectionReusableView+RACSignalSupport.m
│ │ ├── UIControl+RACSignalSupport.h
│ │ ├── UIControl+RACSignalSupport.m
│ │ ├── UIControl+RACSignalSupportPrivate.h
│ │ ├── UIControl+RACSignalSupportPrivate.m
│ │ ├── UIDatePicker+RACSignalSupport.h
│ │ ├── UIDatePicker+RACSignalSupport.m
│ │ ├── UIGestureRecognizer+RACSignalSupport.h
│ │ ├── UIGestureRecognizer+RACSignalSupport.m
│ │ ├── UIImagePickerController+RACSignalSupport.h
│ │ ├── UIImagePickerController+RACSignalSupport.m
│ │ ├── UIRefreshControl+RACCommandSupport.h
│ │ ├── UIRefreshControl+RACCommandSupport.m
│ │ ├── UISegmentedControl+RACSignalSupport.h
│ │ ├── UISegmentedControl+RACSignalSupport.m
│ │ ├── UISlider+RACSignalSupport.h
│ │ ├── UISlider+RACSignalSupport.m
│ │ ├── UIStepper+RACSignalSupport.h
│ │ ├── UIStepper+RACSignalSupport.m
│ │ ├── UISwitch+RACSignalSupport.h
│ │ ├── UISwitch+RACSignalSupport.m
│ │ ├── UITableViewCell+RACSignalSupport.h
│ │ ├── UITableViewCell+RACSignalSupport.m
│ │ ├── UITableViewHeaderFooterView+RACSignalSupport.h
│ │ ├── UITableViewHeaderFooterView+RACSignalSupport.m
│ │ ├── UITextField+RACSignalSupport.h
│ │ ├── UITextField+RACSignalSupport.m
│ │ ├── UITextView+RACSignalSupport.h
│ │ ├── UITextView+RACSignalSupport.m
│ │ └── extobjc/
│ │ ├── RACEXTKeyPathCoding.h
│ │ ├── RACEXTRuntimeExtensions.h
│ │ ├── RACEXTRuntimeExtensions.m
│ │ ├── RACEXTScope.h
│ │ └── RACmetamacros.h
│ ├── SDWebImage/
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── SDWebImage/
│ │ │ ├── Core/
│ │ │ │ ├── NSButton+WebCache.h
│ │ │ │ ├── NSButton+WebCache.m
│ │ │ │ ├── NSData+ImageContentType.h
│ │ │ │ ├── NSData+ImageContentType.m
│ │ │ │ ├── NSImage+Compatibility.h
│ │ │ │ ├── NSImage+Compatibility.m
│ │ │ │ ├── SDAnimatedImage.h
│ │ │ │ ├── SDAnimatedImage.m
│ │ │ │ ├── SDAnimatedImagePlayer.h
│ │ │ │ ├── SDAnimatedImagePlayer.m
│ │ │ │ ├── SDAnimatedImageRep.h
│ │ │ │ ├── SDAnimatedImageRep.m
│ │ │ │ ├── SDAnimatedImageView+WebCache.h
│ │ │ │ ├── SDAnimatedImageView+WebCache.m
│ │ │ │ ├── SDAnimatedImageView.h
│ │ │ │ ├── SDAnimatedImageView.m
│ │ │ │ ├── SDCallbackQueue.h
│ │ │ │ ├── SDCallbackQueue.m
│ │ │ │ ├── SDDiskCache.h
│ │ │ │ ├── SDDiskCache.m
│ │ │ │ ├── SDGraphicsImageRenderer.h
│ │ │ │ ├── SDGraphicsImageRenderer.m
│ │ │ │ ├── SDImageAPNGCoder.h
│ │ │ │ ├── SDImageAPNGCoder.m
│ │ │ │ ├── SDImageAWebPCoder.h
│ │ │ │ ├── SDImageAWebPCoder.m
│ │ │ │ ├── SDImageCache.h
│ │ │ │ ├── SDImageCache.m
│ │ │ │ ├── SDImageCacheConfig.h
│ │ │ │ ├── SDImageCacheConfig.m
│ │ │ │ ├── SDImageCacheDefine.h
│ │ │ │ ├── SDImageCacheDefine.m
│ │ │ │ ├── SDImageCachesManager.h
│ │ │ │ ├── SDImageCachesManager.m
│ │ │ │ ├── SDImageCoder.h
│ │ │ │ ├── SDImageCoder.m
│ │ │ │ ├── SDImageCoderHelper.h
│ │ │ │ ├── SDImageCoderHelper.m
│ │ │ │ ├── SDImageCodersManager.h
│ │ │ │ ├── SDImageCodersManager.m
│ │ │ │ ├── SDImageFrame.h
│ │ │ │ ├── SDImageFrame.m
│ │ │ │ ├── SDImageGIFCoder.h
│ │ │ │ ├── SDImageGIFCoder.m
│ │ │ │ ├── SDImageGraphics.h
│ │ │ │ ├── SDImageGraphics.m
│ │ │ │ ├── SDImageHEICCoder.h
│ │ │ │ ├── SDImageHEICCoder.m
│ │ │ │ ├── SDImageIOAnimatedCoder.h
│ │ │ │ ├── SDImageIOAnimatedCoder.m
│ │ │ │ ├── SDImageIOCoder.h
│ │ │ │ ├── SDImageIOCoder.m
│ │ │ │ ├── SDImageLoader.h
│ │ │ │ ├── SDImageLoader.m
│ │ │ │ ├── SDImageLoadersManager.h
│ │ │ │ ├── SDImageLoadersManager.m
│ │ │ │ ├── SDImageTransformer.h
│ │ │ │ ├── SDImageTransformer.m
│ │ │ │ ├── SDMemoryCache.h
│ │ │ │ ├── SDMemoryCache.m
│ │ │ │ ├── SDWebImageCacheKeyFilter.h
│ │ │ │ ├── SDWebImageCacheKeyFilter.m
│ │ │ │ ├── SDWebImageCacheSerializer.h
│ │ │ │ ├── SDWebImageCacheSerializer.m
│ │ │ │ ├── SDWebImageCompat.h
│ │ │ │ ├── SDWebImageCompat.m
│ │ │ │ ├── SDWebImageDefine.h
│ │ │ │ ├── SDWebImageDefine.m
│ │ │ │ ├── SDWebImageDownloader.h
│ │ │ │ ├── SDWebImageDownloader.m
│ │ │ │ ├── SDWebImageDownloaderConfig.h
│ │ │ │ ├── SDWebImageDownloaderConfig.m
│ │ │ │ ├── SDWebImageDownloaderDecryptor.h
│ │ │ │ ├── SDWebImageDownloaderDecryptor.m
│ │ │ │ ├── SDWebImageDownloaderOperation.h
│ │ │ │ ├── SDWebImageDownloaderOperation.m
│ │ │ │ ├── SDWebImageDownloaderRequestModifier.h
│ │ │ │ ├── SDWebImageDownloaderRequestModifier.m
│ │ │ │ ├── SDWebImageDownloaderResponseModifier.h
│ │ │ │ ├── SDWebImageDownloaderResponseModifier.m
│ │ │ │ ├── SDWebImageError.h
│ │ │ │ ├── SDWebImageError.m
│ │ │ │ ├── SDWebImageIndicator.h
│ │ │ │ ├── SDWebImageIndicator.m
│ │ │ │ ├── SDWebImageManager.h
│ │ │ │ ├── SDWebImageManager.m
│ │ │ │ ├── SDWebImageOperation.h
│ │ │ │ ├── SDWebImageOperation.m
│ │ │ │ ├── SDWebImageOptionsProcessor.h
│ │ │ │ ├── SDWebImageOptionsProcessor.m
│ │ │ │ ├── SDWebImagePrefetcher.h
│ │ │ │ ├── SDWebImagePrefetcher.m
│ │ │ │ ├── SDWebImageTransition.h
│ │ │ │ ├── SDWebImageTransition.m
│ │ │ │ ├── UIButton+WebCache.h
│ │ │ │ ├── UIButton+WebCache.m
│ │ │ │ ├── UIImage+ExtendedCacheData.h
│ │ │ │ ├── UIImage+ExtendedCacheData.m
│ │ │ │ ├── UIImage+ForceDecode.h
│ │ │ │ ├── UIImage+ForceDecode.m
│ │ │ │ ├── UIImage+GIF.h
│ │ │ │ ├── UIImage+GIF.m
│ │ │ │ ├── UIImage+MemoryCacheCost.h
│ │ │ │ ├── UIImage+MemoryCacheCost.m
│ │ │ │ ├── UIImage+Metadata.h
│ │ │ │ ├── UIImage+Metadata.m
│ │ │ │ ├── UIImage+MultiFormat.h
│ │ │ │ ├── UIImage+MultiFormat.m
│ │ │ │ ├── UIImage+Transform.h
│ │ │ │ ├── UIImage+Transform.m
│ │ │ │ ├── UIImageView+HighlightedWebCache.h
│ │ │ │ ├── UIImageView+HighlightedWebCache.m
│ │ │ │ ├── UIImageView+WebCache.h
│ │ │ │ ├── UIImageView+WebCache.m
│ │ │ │ ├── UIView+WebCache.h
│ │ │ │ ├── UIView+WebCache.m
│ │ │ │ ├── UIView+WebCacheOperation.h
│ │ │ │ ├── UIView+WebCacheOperation.m
│ │ │ │ ├── UIView+WebCacheState.h
│ │ │ │ └── UIView+WebCacheState.m
│ │ │ └── Private/
│ │ │ ├── NSBezierPath+SDRoundedCorners.h
│ │ │ ├── NSBezierPath+SDRoundedCorners.m
│ │ │ ├── SDAssociatedObject.h
│ │ │ ├── SDAssociatedObject.m
│ │ │ ├── SDAsyncBlockOperation.h
│ │ │ ├── SDAsyncBlockOperation.m
│ │ │ ├── SDDeviceHelper.h
│ │ │ ├── SDDeviceHelper.m
│ │ │ ├── SDDisplayLink.h
│ │ │ ├── SDDisplayLink.m
│ │ │ ├── SDFileAttributeHelper.h
│ │ │ ├── SDFileAttributeHelper.m
│ │ │ ├── SDImageAssetManager.h
│ │ │ ├── SDImageAssetManager.m
│ │ │ ├── SDImageCachesManagerOperation.h
│ │ │ ├── SDImageCachesManagerOperation.m
│ │ │ ├── SDImageFramePool.h
│ │ │ ├── SDImageFramePool.m
│ │ │ ├── SDImageIOAnimatedCoderInternal.h
│ │ │ ├── SDInternalMacros.h
│ │ │ ├── SDInternalMacros.m
│ │ │ ├── SDWeakProxy.h
│ │ │ ├── SDWeakProxy.m
│ │ │ ├── SDWebImageTransitionInternal.h
│ │ │ ├── SDmetamacros.h
│ │ │ ├── UIColor+SDHexString.h
│ │ │ └── UIColor+SDHexString.m
│ │ └── WebImage/
│ │ ├── PrivacyInfo.xcprivacy
│ │ └── SDWebImage.h
│ ├── Target Support Files/
│ │ ├── CocoaAsyncSocket/
│ │ │ ├── CocoaAsyncSocket-Info.plist
│ │ │ ├── CocoaAsyncSocket-dummy.m
│ │ │ ├── CocoaAsyncSocket-prefix.pch
│ │ │ ├── CocoaAsyncSocket-umbrella.h
│ │ │ ├── CocoaAsyncSocket.debug.xcconfig
│ │ │ ├── CocoaAsyncSocket.modulemap
│ │ │ └── CocoaAsyncSocket.release.xcconfig
│ │ ├── DZNEmptyDataSet/
│ │ │ ├── DZNEmptyDataSet-Info.plist
│ │ │ ├── DZNEmptyDataSet-dummy.m
│ │ │ ├── DZNEmptyDataSet-prefix.pch
│ │ │ ├── DZNEmptyDataSet-umbrella.h
│ │ │ ├── DZNEmptyDataSet.debug.xcconfig
│ │ │ ├── DZNEmptyDataSet.modulemap
│ │ │ └── DZNEmptyDataSet.release.xcconfig
│ │ ├── Masonry/
│ │ │ ├── Masonry-Info.plist
│ │ │ ├── Masonry-dummy.m
│ │ │ ├── Masonry-prefix.pch
│ │ │ ├── Masonry-umbrella.h
│ │ │ ├── Masonry.debug.xcconfig
│ │ │ ├── Masonry.modulemap
│ │ │ └── Masonry.release.xcconfig
│ │ ├── OCMock/
│ │ │ ├── OCMock-Info.plist
│ │ │ ├── OCMock-dummy.m
│ │ │ ├── OCMock-prefix.pch
│ │ │ ├── OCMock-umbrella.h
│ │ │ ├── OCMock.debug.xcconfig
│ │ │ ├── OCMock.modulemap
│ │ │ └── OCMock.release.xcconfig
│ │ ├── Pods-iOS-Network-Stack-Dive/
│ │ │ ├── Pods-iOS-Network-Stack-Dive-Info.plist
│ │ │ ├── Pods-iOS-Network-Stack-Dive-acknowledgements.markdown
│ │ │ ├── Pods-iOS-Network-Stack-Dive-acknowledgements.plist
│ │ │ ├── Pods-iOS-Network-Stack-Dive-dummy.m
│ │ │ ├── Pods-iOS-Network-Stack-Dive-frameworks-Debug-input-files.xcfilelist
│ │ │ ├── Pods-iOS-Network-Stack-Dive-frameworks-Debug-output-files.xcfilelist
│ │ │ ├── Pods-iOS-Network-Stack-Dive-frameworks-Release-input-files.xcfilelist
│ │ │ ├── Pods-iOS-Network-Stack-Dive-frameworks-Release-output-files.xcfilelist
│ │ │ ├── Pods-iOS-Network-Stack-Dive-frameworks.sh
│ │ │ ├── Pods-iOS-Network-Stack-Dive-umbrella.h
│ │ │ ├── Pods-iOS-Network-Stack-Dive.debug.xcconfig
│ │ │ ├── Pods-iOS-Network-Stack-Dive.modulemap
│ │ │ └── Pods-iOS-Network-Stack-Dive.release.xcconfig
│ │ ├── Pods-iOS-Network-Stack-DiveTests/
│ │ │ ├── Pods-iOS-Network-Stack-DiveTests-Info.plist
│ │ │ ├── Pods-iOS-Network-Stack-DiveTests-acknowledgements.markdown
│ │ │ ├── Pods-iOS-Network-Stack-DiveTests-acknowledgements.plist
│ │ │ ├── Pods-iOS-Network-Stack-DiveTests-dummy.m
│ │ │ ├── Pods-iOS-Network-Stack-DiveTests-frameworks-Debug-input-files.xcfilelist
│ │ │ ├── Pods-iOS-Network-Stack-DiveTests-frameworks-Debug-output-files.xcfilelist
│ │ │ ├── Pods-iOS-Network-Stack-DiveTests-frameworks-Release-input-files.xcfilelist
│ │ │ ├── Pods-iOS-Network-Stack-DiveTests-frameworks-Release-output-files.xcfilelist
│ │ │ ├── Pods-iOS-Network-Stack-DiveTests-frameworks.sh
│ │ │ ├── Pods-iOS-Network-Stack-DiveTests-umbrella.h
│ │ │ ├── Pods-iOS-Network-Stack-DiveTests.debug.xcconfig
│ │ │ ├── Pods-iOS-Network-Stack-DiveTests.modulemap
│ │ │ └── Pods-iOS-Network-Stack-DiveTests.release.xcconfig
│ │ ├── Reachability/
│ │ │ ├── Reachability-Info.plist
│ │ │ ├── Reachability-dummy.m
│ │ │ ├── Reachability-prefix.pch
│ │ │ ├── Reachability-umbrella.h
│ │ │ ├── Reachability.debug.xcconfig
│ │ │ ├── Reachability.modulemap
│ │ │ └── Reachability.release.xcconfig
│ │ ├── ReactiveObjC/
│ │ │ ├── ReactiveObjC-Info.plist
│ │ │ ├── ReactiveObjC-dummy.m
│ │ │ ├── ReactiveObjC-prefix.pch
│ │ │ ├── ReactiveObjC-umbrella.h
│ │ │ ├── ReactiveObjC.debug.xcconfig
│ │ │ ├── ReactiveObjC.modulemap
│ │ │ └── ReactiveObjC.release.xcconfig
│ │ ├── SDWebImage/
│ │ │ ├── ResourceBundle-SDWebImage-SDWebImage-Info.plist
│ │ │ ├── SDWebImage-Info.plist
│ │ │ ├── SDWebImage-dummy.m
│ │ │ ├── SDWebImage-prefix.pch
│ │ │ ├── SDWebImage-umbrella.h
│ │ │ ├── SDWebImage.debug.xcconfig
│ │ │ ├── SDWebImage.modulemap
│ │ │ └── SDWebImage.release.xcconfig
│ │ ├── Typhoon/
│ │ │ ├── Typhoon-Info.plist
│ │ │ ├── Typhoon-dummy.m
│ │ │ ├── Typhoon-prefix.pch
│ │ │ ├── Typhoon-umbrella.h
│ │ │ ├── Typhoon.debug.xcconfig
│ │ │ ├── Typhoon.modulemap
│ │ │ └── Typhoon.release.xcconfig
│ │ └── YYModel/
│ │ ├── YYModel-Info.plist
│ │ ├── YYModel-dummy.m
│ │ ├── YYModel-prefix.pch
│ │ ├── YYModel-umbrella.h
│ │ ├── YYModel.debug.xcconfig
│ │ ├── YYModel.modulemap
│ │ └── YYModel.release.xcconfig
│ ├── Typhoon/
│ │ ├── LICENSE
│ │ ├── README.md
│ │ └── Source/
│ │ ├── Configuration/
│ │ │ ├── ConfigPostProcessor/
│ │ │ │ ├── Internal/
│ │ │ │ │ └── TyphoonConfigPostProcessor+Internal.h
│ │ │ │ ├── TyphoonConfigPostProcessor.h
│ │ │ │ ├── TyphoonConfigPostProcessor.m
│ │ │ │ ├── TyphoonConfiguration/
│ │ │ │ │ ├── TyphoonConfiguration.h
│ │ │ │ │ ├── TyphoonJsonStyleConfiguration.h
│ │ │ │ │ ├── TyphoonJsonStyleConfiguration.m
│ │ │ │ │ ├── TyphoonPlistStyleConfiguration.h
│ │ │ │ │ ├── TyphoonPlistStyleConfiguration.m
│ │ │ │ │ ├── TyphoonPropertyStyleConfiguration.h
│ │ │ │ │ └── TyphoonPropertyStyleConfiguration.m
│ │ │ │ ├── TyphoonDefinition+Config.h
│ │ │ │ └── TyphoonDefinition+Config.m
│ │ │ ├── DefinitionOptionConfiguration/
│ │ │ │ ├── Matcher/
│ │ │ │ │ ├── TyphoonOptionMatcher+Internal.h
│ │ │ │ │ ├── TyphoonOptionMatcher.h
│ │ │ │ │ └── TyphoonOptionMatcher.m
│ │ │ │ ├── TyphoonDefinition+Option.h
│ │ │ │ └── TyphoonDefinition+Option.m
│ │ │ ├── GlobalConfigResolver/
│ │ │ │ ├── TyphoonGlobalConfigCollector.h
│ │ │ │ └── TyphoonGlobalConfigCollector.m
│ │ │ ├── Resource/
│ │ │ │ ├── TyphoonBundleResource.h
│ │ │ │ ├── TyphoonBundleResource.m
│ │ │ │ ├── TyphoonPathResource.h
│ │ │ │ ├── TyphoonPathResource.m
│ │ │ │ └── TyphoonResource.h
│ │ │ ├── Startup/
│ │ │ │ ├── TyphoonStartup.h
│ │ │ │ └── TyphoonStartup.m
│ │ │ ├── TyphoonAbstractDetachableComponentFactoryPostProcessor.h
│ │ │ ├── TyphoonAbstractDetachableComponentFactoryPostProcessor.m
│ │ │ ├── TyphoonDefinitionPostProcessor.h
│ │ │ ├── TyphoonInstancePostProcessor.h
│ │ │ └── TyphoonOrdered.h
│ │ ├── Definition/
│ │ │ ├── AutoInjection/
│ │ │ │ ├── TyphoonAutoInjection.h
│ │ │ │ ├── TyphoonFactoryAutoInjectionPostProcessor.h
│ │ │ │ ├── TyphoonFactoryAutoInjectionPostProcessor.m
│ │ │ │ ├── TyphoonInjectedObject.h
│ │ │ │ └── TyphoonInjectedObject.m
│ │ │ ├── Infrastructure/
│ │ │ │ ├── TyphoonDefinition+Infrastructure.h
│ │ │ │ └── TyphoonInjectionsEnumeration.h
│ │ │ ├── Injections/
│ │ │ │ ├── TyphoonAbstractInjection.h
│ │ │ │ ├── TyphoonAbstractInjection.m
│ │ │ │ ├── TyphoonInject.h
│ │ │ │ ├── TyphoonInject.m
│ │ │ │ ├── TyphoonInjection.h
│ │ │ │ ├── TyphoonInjectionByCollection.h
│ │ │ │ ├── TyphoonInjectionByCollection.m
│ │ │ │ ├── TyphoonInjectionByComponentFactory.h
│ │ │ │ ├── TyphoonInjectionByComponentFactory.m
│ │ │ │ ├── TyphoonInjectionByConfig.h
│ │ │ │ ├── TyphoonInjectionByConfig.m
│ │ │ │ ├── TyphoonInjectionByCurrentRuntimeArguments.h
│ │ │ │ ├── TyphoonInjectionByCurrentRuntimeArguments.m
│ │ │ │ ├── TyphoonInjectionByDictionary.h
│ │ │ │ ├── TyphoonInjectionByDictionary.m
│ │ │ │ ├── TyphoonInjectionByFactoryReference.h
│ │ │ │ ├── TyphoonInjectionByFactoryReference.m
│ │ │ │ ├── TyphoonInjectionByObjectFromString.h
│ │ │ │ ├── TyphoonInjectionByObjectFromString.m
│ │ │ │ ├── TyphoonInjectionByObjectInstance.h
│ │ │ │ ├── TyphoonInjectionByObjectInstance.m
│ │ │ │ ├── TyphoonInjectionByReference.h
│ │ │ │ ├── TyphoonInjectionByReference.m
│ │ │ │ ├── TyphoonInjectionByRuntimeArgument.h
│ │ │ │ ├── TyphoonInjectionByRuntimeArgument.m
│ │ │ │ ├── TyphoonInjectionByType.h
│ │ │ │ ├── TyphoonInjectionByType.m
│ │ │ │ ├── TyphoonInjectionContext.h
│ │ │ │ ├── TyphoonInjectionContext.m
│ │ │ │ ├── TyphoonInjections.h
│ │ │ │ ├── TyphoonInjections.m
│ │ │ │ ├── TyphoonParameterInjection.h
│ │ │ │ └── TyphoonPropertyInjection.h
│ │ │ ├── Internal/
│ │ │ │ ├── Collections+CustomInjection.h
│ │ │ │ ├── Collections+CustomInjection.m
│ │ │ │ ├── NSDictionary+CustomInjection.h
│ │ │ │ ├── NSDictionary+CustomInjection.m
│ │ │ │ ├── TyphoonBlockDefinition+InstanceBuilder.h
│ │ │ │ ├── TyphoonBlockDefinition+InstanceBuilder.m
│ │ │ │ ├── TyphoonBlockDefinition+Internal.h
│ │ │ │ ├── TyphoonBlockDefinitionController.h
│ │ │ │ ├── TyphoonBlockDefinitionController.m
│ │ │ │ ├── TyphoonDefinition+InstanceBuilder.h
│ │ │ │ ├── TyphoonDefinition+InstanceBuilder.m
│ │ │ │ ├── TyphoonDefinition+Internal.h
│ │ │ │ ├── TyphoonFactoryDefinition.h
│ │ │ │ ├── TyphoonFactoryDefinition.m
│ │ │ │ ├── TyphoonInjectionDefinition.h
│ │ │ │ ├── TyphoonInjectionDefinition.m
│ │ │ │ ├── TyphoonObjectWithCustomInjection.h
│ │ │ │ ├── TyphoonReferenceDefinition.h
│ │ │ │ └── TyphoonReferenceDefinition.m
│ │ │ ├── Method/
│ │ │ │ ├── Internal/
│ │ │ │ │ ├── TyphoonMethod+InstanceBuilder.h
│ │ │ │ │ └── TyphoonMethod+InstanceBuilder.m
│ │ │ │ ├── TyphoonMethod.h
│ │ │ │ └── TyphoonMethod.m
│ │ │ ├── Namespacing/
│ │ │ │ ├── TyphoonDefinitionNamespace.h
│ │ │ │ └── TyphoonDefinitionNamespace.m
│ │ │ ├── TyphoonBlockDefinition.h
│ │ │ ├── TyphoonBlockDefinition.m
│ │ │ ├── TyphoonDefinition.h
│ │ │ └── TyphoonDefinition.m
│ │ ├── Factory/
│ │ │ ├── Assembly/
│ │ │ │ ├── TyphoonAssembly+TyphoonAssemblyFriend.h
│ │ │ │ ├── TyphoonAssembly.h
│ │ │ │ ├── TyphoonAssembly.m
│ │ │ │ ├── TyphoonAssemblyAccessor.h
│ │ │ │ └── TyphoonAssemblyAccessor.m
│ │ │ ├── Hooks/
│ │ │ │ └── NSObject+FactoryHooks.h
│ │ │ ├── Internal/
│ │ │ │ ├── NSInvocation+TCFCustomImplementation.h
│ │ │ │ ├── NSInvocation+TCFCustomImplementation.m
│ │ │ │ ├── NSInvocation+TCFInstanceBuilder.h
│ │ │ │ ├── NSInvocation+TCFInstanceBuilder.m
│ │ │ │ ├── NSInvocation+TCFUnwrapValues.h
│ │ │ │ ├── NSInvocation+TCFUnwrapValues.m
│ │ │ │ ├── NSInvocation+TCFWrapValues.h
│ │ │ │ ├── NSInvocation+TCFWrapValues.m
│ │ │ │ ├── NSMethodSignature+TCFUnwrapValues.h
│ │ │ │ ├── NSMethodSignature+TCFUnwrapValues.m
│ │ │ │ ├── NSValue+TCFUnwrapValues.h
│ │ │ │ ├── NSValue+TCFUnwrapValues.m
│ │ │ │ ├── TyphoonAssemblyAdviser.h
│ │ │ │ ├── TyphoonAssemblyAdviser.m
│ │ │ │ ├── TyphoonAssemblyBuilder+PlistProcessor.h
│ │ │ │ ├── TyphoonAssemblyBuilder+PlistProcessor.m
│ │ │ │ ├── TyphoonAssemblyBuilder.h
│ │ │ │ ├── TyphoonAssemblyBuilder.m
│ │ │ │ ├── TyphoonAssemblyDefinitionBuilder.h
│ │ │ │ ├── TyphoonAssemblyDefinitionBuilder.m
│ │ │ │ ├── TyphoonAssemblyPropertyInjectionPostProcessor.h
│ │ │ │ ├── TyphoonAssemblyPropertyInjectionPostProcessor.m
│ │ │ │ ├── TyphoonAssemblySelectorAdviser.h
│ │ │ │ ├── TyphoonAssemblySelectorAdviser.m
│ │ │ │ ├── TyphoonBlockComponentFactory.h
│ │ │ │ ├── TyphoonBlockComponentFactory.m
│ │ │ │ ├── TyphoonCallStack.h
│ │ │ │ ├── TyphoonCallStack.m
│ │ │ │ ├── TyphoonCircularDependencyTerminator.h
│ │ │ │ ├── TyphoonCircularDependencyTerminator.m
│ │ │ │ ├── TyphoonCollaboratingAssembliesCollector.h
│ │ │ │ ├── TyphoonCollaboratingAssembliesCollector.m
│ │ │ │ ├── TyphoonCollaboratingAssemblyPropertyEnumerator.h
│ │ │ │ ├── TyphoonCollaboratingAssemblyPropertyEnumerator.m
│ │ │ │ ├── TyphoonCollaboratingAssemblyProxy.h
│ │ │ │ ├── TyphoonCollaboratingAssemblyProxy.m
│ │ │ │ ├── TyphoonComponentFactory+InstanceBuilder.h
│ │ │ │ ├── TyphoonComponentFactory+InstanceBuilder.m
│ │ │ │ ├── TyphoonComponentFactory+TyphoonDefinitionRegisterer.h
│ │ │ │ ├── TyphoonFactoryPropertyInjectionPostProcessor.h
│ │ │ │ ├── TyphoonFactoryPropertyInjectionPostProcessor.m
│ │ │ │ ├── TyphoonMemoryManagementUtils.h
│ │ │ │ ├── TyphoonMemoryManagementUtils.m
│ │ │ │ ├── TyphoonParentReferenceHydratingPostProcessor.h
│ │ │ │ ├── TyphoonParentReferenceHydratingPostProcessor.m
│ │ │ │ ├── TyphoonRuntimeArguments.h
│ │ │ │ ├── TyphoonRuntimeArguments.m
│ │ │ │ ├── TyphoonStackElement.h
│ │ │ │ └── TyphoonStackElement.m
│ │ │ ├── Pool/
│ │ │ │ ├── TyphoonComponentsPool.h
│ │ │ │ ├── TyphoonWeakComponentsPool.h
│ │ │ │ └── TyphoonWeakComponentsPool.m
│ │ │ ├── TyphoonComponentFactory.h
│ │ │ ├── TyphoonComponentFactory.m
│ │ │ ├── TyphoonDefinitionRegisterer.h
│ │ │ ├── TyphoonDefinitionRegisterer.m
│ │ │ ├── TyphoonPreattachedComponentsRegisterer.h
│ │ │ └── TyphoonPreattachedComponentsRegisterer.m
│ │ ├── Test/
│ │ │ ├── Patcher/
│ │ │ │ ├── TyphoonPatcher.h
│ │ │ │ └── TyphoonPatcher.m
│ │ │ └── TestUtils/
│ │ │ ├── TyphoonTestUtils.h
│ │ │ └── TyphoonTestUtils.m
│ │ ├── TypeConversion/
│ │ │ ├── Converters/
│ │ │ │ ├── NSNullTypeConverter.h
│ │ │ │ ├── NSNullTypeConverter.m
│ │ │ │ ├── TyphoonNSNumberTypeConverter.h
│ │ │ │ ├── TyphoonNSNumberTypeConverter.m
│ │ │ │ ├── TyphoonNSURLTypeConverter.h
│ │ │ │ ├── TyphoonNSURLTypeConverter.m
│ │ │ │ ├── TyphoonPassThroughTypeConverter.h
│ │ │ │ ├── TyphoonPassThroughTypeConverter.m
│ │ │ │ ├── TyphoonPrimitiveTypeConverter.h
│ │ │ │ └── TyphoonPrimitiveTypeConverter.m
│ │ │ ├── Helpers/
│ │ │ │ ├── TyphoonColorConversionUtils.h
│ │ │ │ └── TyphoonColorConversionUtils.m
│ │ │ ├── TyphoonTypeConversionUtils.h
│ │ │ ├── TyphoonTypeConversionUtils.m
│ │ │ ├── TyphoonTypeConverter.h
│ │ │ ├── TyphoonTypeConverterRegistry.h
│ │ │ ├── TyphoonTypeConverterRegistry.m
│ │ │ ├── TyphoonTypeDescriptor.h
│ │ │ └── TyphoonTypeDescriptor.m
│ │ ├── Typhoon+Infrastructure.h
│ │ ├── Typhoon.h
│ │ ├── Utils/
│ │ │ ├── NSArray+TyphoonManualEnumeration.h
│ │ │ ├── NSArray+TyphoonManualEnumeration.m
│ │ │ ├── NSObject+DeallocNotification.h
│ │ │ ├── NSObject+DeallocNotification.m
│ │ │ ├── NSObject+PropertyInjection.h
│ │ │ ├── NSObject+PropertyInjection.m
│ │ │ ├── NSObject+TyphoonIntrospectionUtils.h
│ │ │ ├── NSObject+TyphoonIntrospectionUtils.m
│ │ │ ├── Swizzle/
│ │ │ │ ├── TyphoonMethodSwizzler.h
│ │ │ │ ├── TyphoonSwizzlerDefaultImpl.h
│ │ │ │ └── TyphoonSwizzlerDefaultImpl.m
│ │ │ ├── TyphoonIntrospectionUtils.h
│ │ │ ├── TyphoonIntrospectionUtils.m
│ │ │ ├── TyphoonLinkerCategoryBugFix.h
│ │ │ ├── TyphoonSelector.h
│ │ │ ├── TyphoonSelector.m
│ │ │ └── TyphoonUtils.h
│ │ ├── Vendor/
│ │ │ └── OCLogTemplate/
│ │ │ └── OCLogTemplate.h
│ │ └── ios/
│ │ ├── Configuration/
│ │ │ ├── TyphoonViewControllerInjector.h
│ │ │ └── TyphoonViewControllerInjector.m
│ │ ├── Definition/
│ │ │ ├── TyphoonStoryboardDefinition.h
│ │ │ ├── TyphoonStoryboardDefinition.m
│ │ │ ├── TyphoonStoryboardDefinitionContext.h
│ │ │ └── TyphoonStoryboardDefinitionContext.m
│ │ ├── Nib/
│ │ │ ├── TyphoonNibLoader.h
│ │ │ ├── TyphoonNibLoader.m
│ │ │ ├── TyphoonViewControllerNibResolver.h
│ │ │ └── TyphoonViewControllerNibResolver.m
│ │ ├── Storyboard/
│ │ │ ├── Internal/
│ │ │ │ ├── UIView+TyphoonDefinitionKey.h
│ │ │ │ ├── UIView+TyphoonDefinitionKey.m
│ │ │ │ ├── UIViewController+TyphoonStoryboardIntegration.h
│ │ │ │ └── UIViewController+TyphoonStoryboardIntegration.m
│ │ │ ├── NSLayoutConstraint+TyphoonOutletTransfer.h
│ │ │ ├── NSLayoutConstraint+TyphoonOutletTransfer.m
│ │ │ ├── TyphoonComponentFactory+Storyboard.h
│ │ │ ├── TyphoonComponentFactory+Storyboard.m
│ │ │ ├── TyphoonDefinition+Storyboard.h
│ │ │ ├── TyphoonDefinition+Storyboard.m
│ │ │ ├── TyphoonLoadedView.h
│ │ │ ├── TyphoonLoadedView.m
│ │ │ ├── TyphoonStoryboard.h
│ │ │ ├── TyphoonStoryboard.m
│ │ │ ├── TyphoonStoryboardProvider.h
│ │ │ ├── TyphoonStoryboardProvider.m
│ │ │ ├── TyphoonStoryboardResolver.h
│ │ │ ├── TyphoonStoryboardResolver.m
│ │ │ ├── TyphoonViewControllerFactory.h
│ │ │ ├── TyphoonViewControllerFactory.m
│ │ │ ├── TyphoonViewHelpers.h
│ │ │ ├── TyphoonViewHelpers.m
│ │ │ ├── UIResponder+TyphoonOutletTransfer.h
│ │ │ ├── UIResponder+TyphoonOutletTransfer.m
│ │ │ ├── UIView+TyphoonOutletTransfer.h
│ │ │ └── UIView+TyphoonOutletTransfer.m
│ │ ├── TypeConversion/
│ │ │ └── Converters/
│ │ │ ├── TyphoonBundledImageTypeConverter.h
│ │ │ ├── TyphoonBundledImageTypeConverter.m
│ │ │ ├── TyphoonUIColorTypeConverter.h
│ │ │ └── TyphoonUIColorTypeConverter.m
│ │ └── TyphooniOS.h
│ └── YYModel/
│ ├── LICENSE
│ ├── README.md
│ └── YYModel/
│ ├── NSObject+YYModel.h
│ ├── NSObject+YYModel.m
│ ├── YYClassInfo.h
│ ├── YYClassInfo.m
│ └── YYModel.h
├── README.en.md
├── README.md
├── iOS-Network-Stack-Dive/
│ ├── ArchitectureExtensions/
│ │ ├── AOP/
│ │ │ ├── LoggingAspect/
│ │ │ │ ├── TJPAspectCore.h
│ │ │ │ ├── TJPAspectCore.m
│ │ │ │ ├── TJPLogAspectInterface.h
│ │ │ │ ├── TJPLogModel.h
│ │ │ │ ├── TJPLogModel.m
│ │ │ │ ├── TJPLogger.h
│ │ │ │ ├── TJPLogger.m
│ │ │ │ ├── TJPLoggerManager.h
│ │ │ │ └── TJPLoggerManager.m
│ │ │ ├── TJPLoggerViewController.h
│ │ │ └── TJPLoggerViewController.m
│ │ ├── NetworkMonitor/
│ │ │ ├── Collector/
│ │ │ │ ├── TJPMetricsCollector.h
│ │ │ │ └── TJPMetricsCollector.m
│ │ │ ├── Metrics/
│ │ │ │ ├── TJPConcreteSession+TJPMetrics.h
│ │ │ │ ├── TJPConcreteSession+TJPMetrics.m
│ │ │ │ ├── TJPConnectStateMachine+TJPMetrics.h
│ │ │ │ ├── TJPConnectStateMachine+TJPMetrics.m
│ │ │ │ ├── TJPConnectionManager+TJPMetrics.h
│ │ │ │ ├── TJPConnectionManager+TJPMetrics.m
│ │ │ │ ├── TJPDynamicHeartbeat+TJPMetrics.h
│ │ │ │ ├── TJPDynamicHeartbeat+TJPMetrics.m
│ │ │ │ ├── TJPMessageParser+TJPMetrics.h
│ │ │ │ └── TJPMessageParser+TJPMetrics.m
│ │ │ ├── MetricsHeader/
│ │ │ │ ├── TJPMetricsKeys.h
│ │ │ │ └── TJPMetricsKeys.m
│ │ │ ├── Reporter/
│ │ │ │ ├── TJPMetricsConsoleReporter.h
│ │ │ │ └── TJPMetricsConsoleReporter.m
│ │ │ ├── TJPNetworkMonitorViewController.h
│ │ │ ├── TJPNetworkMonitorViewController.m
│ │ │ ├── TJPSessionPacketMonitor.h
│ │ │ └── TJPSessionPacketMonitor.m
│ │ ├── Utility/
│ │ │ ├── Category/
│ │ │ │ ├── UIImage+TJPImageOrientation.h
│ │ │ │ └── UIImage+TJPImageOrientation.m
│ │ │ └── TJPUtility/
│ │ │ ├── TJPFPSLabel.h
│ │ │ └── TJPFPSLabel.m
│ │ └── VIPER-Integration/
│ │ ├── CustomTableViewDemo/
│ │ │ ├── TJPCustomTableViewDemoViewController.h
│ │ │ ├── TJPCustomTableViewDemoViewController.m
│ │ │ ├── TJPSectionTableViewDemoViewController.h
│ │ │ ├── TJPSectionTableViewDemoViewController.m
│ │ │ └── TJPViperBaseTableView/
│ │ │ ├── Cell/
│ │ │ │ ├── TJPBaseTableViewCell.h
│ │ │ │ └── TJPBaseTableViewCell.m
│ │ │ ├── Loading/
│ │ │ │ ├── TJPDefaultLoadingAnimation.h
│ │ │ │ └── TJPDefaultLoadingAnimation.m
│ │ │ ├── Model/
│ │ │ │ ├── TJPBaseCellModel.h
│ │ │ │ ├── TJPBaseCellModel.m
│ │ │ │ ├── TJPBaseSectionModel.h
│ │ │ │ └── TJPBaseSectionModel.m
│ │ │ ├── Protocol/
│ │ │ │ ├── TJPBaseCellModelProtocol.h
│ │ │ │ ├── TJPBaseSectionModelProtocol.h
│ │ │ │ ├── TJPBaseTableViewCellProtocol.h
│ │ │ │ └── TJPBaseTableViewLoadingProtocol.h
│ │ │ └── View/
│ │ │ ├── TJPBaseTableView.h
│ │ │ └── TJPBaseTableView.m
│ │ ├── DIContainer/
│ │ │ ├── TJPViperModuleAssembly.h
│ │ │ ├── TJPViperModuleAssembly.m
│ │ │ └── TJPViperModuleProvider.h
│ │ ├── VIPER-Architecture/
│ │ │ ├── Entity/
│ │ │ │ ├── TJPNavigationModel.h
│ │ │ │ └── TJPNavigationModel.m
│ │ │ ├── Error/
│ │ │ │ ├── TJPViperDefaultErrorHandler.h
│ │ │ │ ├── TJPViperDefaultErrorHandler.m
│ │ │ │ ├── TJPViperErrorDefine.h
│ │ │ │ ├── TJPViperErrorHandlingStrategy.h
│ │ │ │ └── TJPViperErrorHandlingStrategy.m
│ │ │ ├── Handler/
│ │ │ │ ├── TJPViperErrorHandlerDelegate.h
│ │ │ │ └── TJPViperErrorHandlerProtocol.h
│ │ │ ├── Interactor/
│ │ │ │ ├── TJPViperBaseInteractorImpl.h
│ │ │ │ ├── TJPViperBaseInteractorImpl.m
│ │ │ │ └── TJPViperBaseInteractorProtocol.h
│ │ │ ├── Presenter/
│ │ │ │ ├── TJPViperBasePresenterImpl.h
│ │ │ │ ├── TJPViperBasePresenterImpl.m
│ │ │ │ └── TJPViperBasePresenterProtocol.h
│ │ │ ├── Response/
│ │ │ │ ├── TJPBaseResponse.h
│ │ │ │ └── TJPBaseResponse.m
│ │ │ ├── Router/
│ │ │ │ ├── TJPNavigationCoordinator.h
│ │ │ │ ├── TJPNavigationCoordinator.m
│ │ │ │ ├── TJPNavigationDefines.h
│ │ │ │ ├── TJPViewPresentHandler.h
│ │ │ │ ├── TJPViewPresentHandler.m
│ │ │ │ ├── TJPViewPushHandler.h
│ │ │ │ ├── TJPViewPushHandler.m
│ │ │ │ ├── TJPViperBaseRouterHandlerProtocol.h
│ │ │ │ ├── TJPViperBaseRouterImpl.h
│ │ │ │ └── TJPViperBaseRouterImpl.m
│ │ │ ├── StateMachine/
│ │ │ │ ├── TJPViewControllerStateMachine.h
│ │ │ │ └── TJPViewControllerStateMachine.m
│ │ │ └── View/
│ │ │ ├── TJPViperBaseTableViewController.h
│ │ │ ├── TJPViperBaseTableViewController.m
│ │ │ └── TJPViperBaseViewControllerProtocol.h
│ │ └── VIPER-Demo/
│ │ └── Demo/
│ │ ├── Cell/
│ │ │ ├── TJPAdCell.h
│ │ │ ├── TJPAdCell.m
│ │ │ ├── TJPImageCell.h
│ │ │ ├── TJPImageCell.m
│ │ │ ├── TJPNewsCell.h
│ │ │ ├── TJPNewsCell.m
│ │ │ ├── TJPProductCell.h
│ │ │ ├── TJPProductCell.m
│ │ │ ├── TJPUserDynamicCell.h
│ │ │ ├── TJPUserDynamicCell.m
│ │ │ ├── TJPVideoCell.h
│ │ │ └── TJPVideoCell.m
│ │ ├── Entity/
│ │ │ ├── TJPAdCellModel.h
│ │ │ ├── TJPAdCellModel.m
│ │ │ ├── TJPImageCellModel.h
│ │ │ ├── TJPImageCellModel.m
│ │ │ ├── TJPNewsCellModel.h
│ │ │ ├── TJPNewsCellModel.m
│ │ │ ├── TJPProductCellModel.h
│ │ │ ├── TJPProductCellModel.m
│ │ │ ├── TJPUserDynamicCellModel.h
│ │ │ ├── TJPUserDynamicCellModel.m
│ │ │ ├── TJPVideoCellModel.h
│ │ │ └── TJPVideoCellModel.m
│ │ ├── Interactor/
│ │ │ ├── TJPVIPERDemoInteractorImpl.h
│ │ │ └── TJPVIPERDemoInteractorImpl.m
│ │ ├── Presenter/
│ │ │ ├── TJPVIPERDemoPresenter.h
│ │ │ └── TJPVIPERDemoPresenter.m
│ │ ├── Respone/
│ │ │ ├── TJPFeedResponse.h
│ │ │ ├── TJPFeedResponse.m
│ │ │ ├── TJPPaginationInfo.h
│ │ │ └── TJPPaginationInfo.m
│ │ ├── Router/
│ │ │ ├── TJPVIPERDemoRouter.h
│ │ │ └── TJPVIPERDemoRouter.m
│ │ └── View/
│ │ ├── DetailVC/
│ │ │ ├── TJPNewsDetailTableViewController.h
│ │ │ └── TJPNewsDetailTableViewController.m
│ │ ├── TJPLikeCommentAreaView.h
│ │ ├── TJPLikeCommentAreaView.m
│ │ ├── TJPNineGridImageView.h
│ │ ├── TJPNineGridImageView.m
│ │ ├── TJPVIPERDemoDetailViewController.h
│ │ ├── TJPVIPERDemoDetailViewController.m
│ │ ├── TJPVIPERDemoViewController.h
│ │ └── TJPVIPERDemoViewController.m
│ ├── Assets.xcassets/
│ │ ├── AccentColor.colorset/
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset/
│ │ │ └── Contents.json
│ │ └── Contents.json
│ ├── Base.lproj/
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── CoreNetworkStack/
│ │ ├── TJPIMCore/
│ │ │ ├── Bulider/
│ │ │ │ ├── TJPMessageBuilder.h
│ │ │ │ └── TJPMessageBuilder.m
│ │ │ ├── Connect/
│ │ │ │ ├── TJPConnectionManager.h
│ │ │ │ └── TJPConnectionManager.m
│ │ │ ├── Container/
│ │ │ │ ├── TJPConcreteSession.h
│ │ │ │ ├── TJPConcreteSession.m
│ │ │ │ ├── TJPLightweightSessionPool.h
│ │ │ │ ├── TJPLightweightSessionPool.m
│ │ │ │ ├── TJPNetworkCoordinator.h
│ │ │ │ └── TJPNetworkCoordinator.m
│ │ │ ├── Context/
│ │ │ │ ├── TJPMessageContext.h
│ │ │ │ └── TJPMessageContext.m
│ │ │ ├── Error/
│ │ │ │ ├── TJPErrorUtil.h
│ │ │ │ ├── TJPErrorUtil.m
│ │ │ │ ├── TJPNETError.h
│ │ │ │ ├── TJPNETError.m
│ │ │ │ ├── TJPNETErrorHandler.h
│ │ │ │ └── TJPNETErrorHandler.m
│ │ │ ├── Heartbeat/
│ │ │ │ ├── TJPDynamicHeartbeat.h
│ │ │ │ └── TJPDynamicHeartbeat.m
│ │ │ ├── IMClient/
│ │ │ │ ├── TJPIMClient.h
│ │ │ │ └── TJPIMClient.m
│ │ │ ├── Log/
│ │ │ │ ├── TJPLogManager.h
│ │ │ │ └── TJPLogManager.m
│ │ │ ├── Message/
│ │ │ │ ├── TJPMessageFactory.h
│ │ │ │ ├── TJPMessageFactory.m
│ │ │ │ ├── TJPMessageManager.h
│ │ │ │ ├── TJPMessageManager.m
│ │ │ │ ├── TJPMessageSerializer.h
│ │ │ │ ├── TJPMessageSerializer.m
│ │ │ │ ├── TJPTextMessage.h
│ │ │ │ └── TJPTextMessage.m
│ │ │ ├── Network/
│ │ │ │ ├── TJPNetworkCondition.h
│ │ │ │ └── TJPNetworkCondition.m
│ │ │ ├── NetworkUtility/
│ │ │ │ ├── TJPNetworkDefine.h
│ │ │ │ └── TJPNetworkErrorDefine.h
│ │ │ ├── Parser/
│ │ │ │ ├── TJPMessageParser.h
│ │ │ │ ├── TJPMessageParser.m
│ │ │ │ ├── TJPParsedPacket.h
│ │ │ │ ├── TJPParsedPacket.m
│ │ │ │ ├── TJPRingBuffer.h
│ │ │ │ └── TJPRingBuffer.m
│ │ │ ├── Policy/
│ │ │ │ ├── TJPReconnectPolicy.h
│ │ │ │ └── TJPReconnectPolicy.m
│ │ │ ├── Protocol/
│ │ │ │ ├── TJPConnectionDelegate.h
│ │ │ │ ├── TJPHeartbeatProtocol.h
│ │ │ │ ├── TJPMessageManagerDelegate.h
│ │ │ │ ├── TJPMessageManagerNetworkDelegate.h
│ │ │ │ ├── TJPMessageProtocol.h
│ │ │ │ ├── TJPSessionDelegate.h
│ │ │ │ └── TJPSessionProtocol.h
│ │ │ ├── Sequence/
│ │ │ │ ├── TJPSequenceManager.h
│ │ │ │ └── TJPSequenceManager.m
│ │ │ ├── TCP-Machine/
│ │ │ │ ├── TJPConnectStateMachine.h
│ │ │ │ ├── TJPConnectStateMachine.m
│ │ │ │ ├── TJPMessageStateMachine.h
│ │ │ │ └── TJPMessageStateMachine.m
│ │ │ └── Utility/
│ │ │ ├── TJPCoreTypes.h
│ │ │ ├── TJPNetworkConfig.h
│ │ │ ├── TJPNetworkConfig.m
│ │ │ ├── TJPNetworkUtil.h
│ │ │ └── TJPNetworkUtil.m
│ │ ├── TransportLayer/
│ │ │ └── Tests/
│ │ │ ├── TJPMockFinalVersionTCPServer.h
│ │ │ ├── TJPMockFinalVersionTCPServer.m
│ │ │ ├── TJPMockTCPServer.h
│ │ │ └── TJPMockTCPServer.m
│ │ ├── V1_BasicFunction/
│ │ │ ├── TJPNetworkManagerV1.h
│ │ │ └── TJPNetworkManagerV1.m
│ │ └── V2_Concurrency/
│ │ ├── TJPConcurrentNetworkManager.h
│ │ ├── TJPConcurrentNetworkManager.m
│ │ └── TJPNetworkProtocol.h
│ ├── Delegate/
│ │ ├── AppDelegate.h
│ │ └── AppDelegate.m
│ ├── Docs/
│ │ ├── ArchitectureExtensions/
│ │ │ └── AspectLoggerDesign.md
│ │ ├── CoreNetworkStackDoc/
│ │ │ ├── FinalNetworkTests.md
│ │ │ ├── HeartbeatKeepaliveDesign.md
│ │ │ ├── NetworkMonitorDesign.md
│ │ │ ├── ProtocolParseDesign.md
│ │ │ ├── TJPNetworkManagerV2Design.md
│ │ │ ├── TJPNetworkV3FinalDesign.md
│ │ │ └── 轻量级多路复用架构设计.md
│ │ ├── VIPER-Integration/
│ │ │ ├── VIPER-Design.md
│ │ │ └── VIPER-RouterGuide.md
│ │ └── Version/
│ │ └── v1.2.0版本内容.md
│ ├── HomeVC/
│ │ ├── HomeViewController.h
│ │ └── HomeViewController.m
│ ├── Info.plist
│ ├── Labs/
│ │ └── NetworkFundamentals/
│ │ └── Lab-Socket-API/
│ │ ├── StickyPacketDemo/
│ │ │ ├── SocketChatClient.h
│ │ │ ├── SocketChatClient.m
│ │ │ ├── SocketChatServer.h
│ │ │ ├── SocketChatServer.m
│ │ │ ├── StickPacketDemoController.h
│ │ │ └── StickPacketDemoController.m
│ │ └── StickyPacketSolution/
│ │ ├── SolutionStickyPacketClient.h
│ │ ├── SolutionStickyPacketClient.m
│ │ ├── SolutionStickyPacketServer.h
│ │ ├── SolutionStickyPacketServer.m
│ │ ├── StickPacketSolutionController.h
│ │ └── StickPacketSolutionController.m
│ ├── ProductionBridge/
│ │ └── VIPER-Sample/
│ │ └── MessageModule/
│ │ ├── Entity/
│ │ │ ├── TJPChatMessage.h
│ │ │ └── TJPChatMessage.m
│ │ ├── Manager/
│ │ │ ├── TJPMessageTimeoutManager.h
│ │ │ └── TJPMessageTimeoutManager.m
│ │ ├── TJPChatViewController.h
│ │ ├── TJPChatViewController.m
│ │ ├── Utility/
│ │ │ └── TJPChatMessageDefine.h
│ │ └── View/
│ │ ├── TJPChatInputView.h
│ │ ├── TJPChatInputView.m
│ │ ├── TJPChatMessageCell.h
│ │ ├── TJPChatMessageCell.m
│ │ ├── TJPConnectionStatusView.h
│ │ ├── TJPConnectionStatusView.m
│ │ ├── TJPMessageStatusIndicator.h
│ │ └── TJPMessageStatusIndicator.m
│ ├── Resources/
│ │ ├── feedData.json
│ │ ├── feedData_page2.json
│ │ └── feedData_page3.json
│ ├── Tools/
│ │ ├── Color/
│ │ │ ├── UIColor+TJPColor.h
│ │ │ └── UIColor+TJPColor.m
│ │ ├── MJRefresh/
│ │ │ ├── Base/
│ │ │ │ ├── MJRefreshAutoFooter.h
│ │ │ │ ├── MJRefreshAutoFooter.m
│ │ │ │ ├── MJRefreshBackFooter.h
│ │ │ │ ├── MJRefreshBackFooter.m
│ │ │ │ ├── MJRefreshComponent.h
│ │ │ │ ├── MJRefreshComponent.m
│ │ │ │ ├── MJRefreshFooter.h
│ │ │ │ ├── MJRefreshFooter.m
│ │ │ │ ├── MJRefreshHeader.h
│ │ │ │ └── MJRefreshHeader.m
│ │ │ ├── Custom/
│ │ │ │ ├── Footer/
│ │ │ │ │ ├── Auto/
│ │ │ │ │ │ ├── MJRefreshAutoGifFooter.h
│ │ │ │ │ │ ├── MJRefreshAutoGifFooter.m
│ │ │ │ │ │ ├── MJRefreshAutoNormalFooter.h
│ │ │ │ │ │ ├── MJRefreshAutoNormalFooter.m
│ │ │ │ │ │ ├── MJRefreshAutoStateFooter.h
│ │ │ │ │ │ └── MJRefreshAutoStateFooter.m
│ │ │ │ │ └── Back/
│ │ │ │ │ ├── MJRefreshBackGifFooter.h
│ │ │ │ │ ├── MJRefreshBackGifFooter.m
│ │ │ │ │ ├── MJRefreshBackNormalFooter.h
│ │ │ │ │ ├── MJRefreshBackNormalFooter.m
│ │ │ │ │ ├── MJRefreshBackStateFooter.h
│ │ │ │ │ └── MJRefreshBackStateFooter.m
│ │ │ │ └── Header/
│ │ │ │ ├── MJRefreshGifHeader.h
│ │ │ │ ├── MJRefreshGifHeader.m
│ │ │ │ ├── MJRefreshNormalHeader.h
│ │ │ │ ├── MJRefreshNormalHeader.m
│ │ │ │ ├── MJRefreshStateHeader.h
│ │ │ │ └── MJRefreshStateHeader.m
│ │ │ ├── MJRefresh.bundle/
│ │ │ │ ├── en.lproj/
│ │ │ │ │ └── Localizable.strings
│ │ │ │ ├── zh-Hans.lproj/
│ │ │ │ │ └── Localizable.strings
│ │ │ │ └── zh-Hant.lproj/
│ │ │ │ └── Localizable.strings
│ │ │ ├── MJRefresh.h
│ │ │ ├── MJRefreshConst.h
│ │ │ ├── MJRefreshConst.m
│ │ │ ├── NSBundle+MJRefresh.h
│ │ │ ├── NSBundle+MJRefresh.m
│ │ │ ├── UIScrollView+MJExtension.h
│ │ │ ├── UIScrollView+MJExtension.m
│ │ │ ├── UIScrollView+MJRefresh.h
│ │ │ ├── UIScrollView+MJRefresh.m
│ │ │ ├── UIView+MJExtension.h
│ │ │ └── UIView+MJExtension.m
│ │ ├── TJPCache/
│ │ │ ├── TJPCacheManager.h
│ │ │ ├── TJPCacheManager.m
│ │ │ ├── TJPCacheProtocol.h
│ │ │ ├── TJPDiskCache.h
│ │ │ ├── TJPDiskCache.m
│ │ │ ├── TJPMemoryCache.h
│ │ │ └── TJPMemoryCache.m
│ │ └── TJPUITools/
│ │ ├── TJPToast.h
│ │ └── TJPToast.m
│ └── main.m
├── iOS-Network-Stack-Dive.xcodeproj/
│ ├── project.pbxproj
│ ├── project.xcworkspace/
│ │ └── contents.xcworkspacedata
│ ├── xcshareddata/
│ │ └── xcschemes/
│ │ └── iOS-Network-Stack-Dive.xcscheme
│ └── xcuserdata/
│ └── aarongtang.xcuserdatad/
│ └── xcschemes/
│ └── xcschememanagement.plist
├── iOS-Network-Stack-Dive.xcworkspace/
│ ├── contents.xcworkspacedata
│ └── xcuserdata/
│ └── aarongtang.xcuserdatad/
│ └── xcdebugger/
│ └── Breakpoints_v2.xcbkptlist
└── iOS-Network-Stack-DiveTests/
├── ArchitectureExtensions/
│ ├── AOP/
│ │ └── TJPLoggerTests.m
│ ├── Network/
│ │ └── TJPConnectStateMachineMetricsTest.m
│ └── VIPER/
│ └── TJPNavigationCoordinatorTests.m
├── CoreNetworkStack/
│ └── TransportLayer/
│ ├── V1_BasicFunction/
│ │ └── TJPNetworkManagerTests.m
│ ├── V2_Concurrency/
│ │ └── TJPNetworkManagerV2Tests.m
│ └── V3_FinalProduct/
│ ├── TJPConcreteSessionTests.m
│ ├── TJPConnectStateMachineTests.m
│ ├── TJPDynamicHeartbeatTests.m
│ ├── TJPMessageContextTests.m
│ ├── TJPMessageParserTests.m
│ ├── TJPNetworkConditionTests.m
│ ├── TJPNetworkCoordinatorTests.m
│ ├── TJPReconnectPolicyTests.m
│ ├── TJPSequenceManagerTests.m
│ └── TJPTJPNetworkUtilTests.m
└── iOS_Network_Stack_DiveTests.m
================================================
FILE CONTENTS
================================================
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2025 [唐佳鹏 or AarongTang]
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.
================================================
FILE: Podfile
================================================
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
target 'iOS-Network-Stack-Dive' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
# Pods for iOS-Network-Stack-Dive
pod 'CocoaAsyncSocket'
pod 'Reachability', '3.7.5'
# pod 'libffi-ios-latest', :git => 'https://github.com/codeacmen/libffi-ios-latest.git', :tag => '3.4.0-fix'
pod 'ReactiveObjC'
pod 'Typhoon'
#UI pod
# pod 'MJRefresh'
pod 'DZNEmptyDataSet'
pod 'Masonry'
pod 'SDWebImage'
# pod 'YYKit'
pod 'YYModel'
target 'iOS-Network-Stack-DiveTests' do
inherit! :search_paths
#Mock pod
pod 'OCMock'
end
end
================================================
FILE: Pods/CocoaAsyncSocket/LICENSE.txt
================================================
This library is in the public domain.
However, not all organizations are allowed to use such a license.
For example, Germany doesn't recognize the Public Domain and one is not allowed to use libraries under such license (or similar).
Thus, the library is now dual licensed,
and one is allowed to choose which license they would like to use.
##################################################
License Option #1 :
##################################################
Public Domain
##################################################
License Option #2 :
##################################################
Software License Agreement (BSD License)
Copyright (c) 2017, 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 LLC 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.
================================================
FILE: Pods/CocoaAsyncSocket/README.markdown
================================================
# CocoaAsyncSocket
[](https://travis-ci.org/robbiehanson/CocoaAsyncSocket) [](http://cocoadocs.org/docsets/CocoaAsyncSocket) [](https://github.com/Carthage/Carthage) [](http://cocoapods.org/?q=CocoaAsyncSocket) [](https://en.wikipedia.org/wiki/Public_domain)
CocoaAsyncSocket provides easy-to-use and powerful asynchronous socket libraries for macOS, iOS, and tvOS. The classes are described below.
## Installation
#### CocoaPods
Install using [CocoaPods](https://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.
#### Swift Package Manager
Simply add the package dependency to your Package.swift and depend on "CocoaAsyncSocket" in the necessary targets:
```swift
dependencies: [
.package(url: "https://github.com/robbiehanson/CocoaAsyncSocket", from: "7.6.4")
]
```
#### 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
// When using Clang Modules:
@import CocoaAsyncSocket;
// or when not:
#import "GCDAsyncSocket.h" // for TCP
#import "GCDAsyncUdpSocket.h" // for UDP
```
Using Swift:
```swift
import CocoaAsyncSocket
```
## TCP
**GCDAsyncSocket** is a TCP/IP socket networking library built atop Grand Central Dispatch. Here are the key features available:
- Native Objective-C, fully self-contained in one class.
_No need to muck around with sockets or streams. This class handles everything for you._
- Full delegate support
_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.
_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.
_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.
_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
_Secure your socket with ease using just a single method call. Available for both client and server sockets._
- Fully GCD based and Thread-Safe
_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._
## UDP
**GCDAsyncUdpSocket** is a UDP/IP socket networking library built atop Grand Central Dispatch. Here are the key features available:
- Native Objective-C, fully self-contained in one class.
_No need to muck around with low-level sockets. This class handles everything for you._
- Full delegate support.
_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.
_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.
_Automatically send/recv using IPv4 and/or IPv6. No more worrying about multiple sockets._
- Fully GCD based and Thread-Safe
_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._
***
For those new(ish) to networking, it's recommended you **[read the wiki](https://github.com/robbiehanson/CocoaAsyncSocket/wiki)**.
_Sockets might not work exactly like you think they do..._
**Still got questions?** Try the **[CocoaAsyncSocket Mailing List](https://groups.google.com/group/cocoaasyncsocket)**.
***
Love the project? Wanna buy me a ☕️ ? (or a 🍺 😀 ):
[](https://onename.com/robbiehanson)
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=2M8C699FQ8AW2)
================================================
FILE: Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncSocket.h
================================================
//
// GCDAsyncSocket.h
//
// This class is in the public domain.
// Originally created by Robbie Hanson in Q3 2010.
// Updated and maintained by Deusty LLC and the Apple development community.
//
// https://github.com/robbiehanson/CocoaAsyncSocket
//
#import
#import
#import
#import
#import
#include // AF_INET, AF_INET6
@class GCDAsyncReadPacket;
@class GCDAsyncWritePacket;
@class GCDAsyncSocketPreBuffer;
@protocol GCDAsyncSocketDelegate;
NS_ASSUME_NONNULL_BEGIN
extern NSString *const GCDAsyncSocketException;
extern NSString *const GCDAsyncSocketErrorDomain;
extern NSString *const GCDAsyncSocketQueueName;
extern NSString *const GCDAsyncSocketThreadName;
extern NSString *const GCDAsyncSocketManuallyEvaluateTrust;
#if TARGET_OS_IPHONE
extern NSString *const GCDAsyncSocketUseCFStreamForTLS;
#endif
#define GCDAsyncSocketSSLPeerName (NSString *)kCFStreamSSLPeerName
#define GCDAsyncSocketSSLCertificates (NSString *)kCFStreamSSLCertificates
#define GCDAsyncSocketSSLIsServer (NSString *)kCFStreamSSLIsServer
extern NSString *const GCDAsyncSocketSSLPeerID;
extern NSString *const GCDAsyncSocketSSLProtocolVersionMin;
extern NSString *const GCDAsyncSocketSSLProtocolVersionMax;
extern NSString *const GCDAsyncSocketSSLSessionOptionFalseStart;
extern NSString *const GCDAsyncSocketSSLSessionOptionSendOneByteRecord;
extern NSString *const GCDAsyncSocketSSLCipherSuites;
extern NSString *const GCDAsyncSocketSSLALPN;
#if !TARGET_OS_IPHONE
extern NSString *const GCDAsyncSocketSSLDiffieHellmanParameters;
#endif
#define GCDAsyncSocketLoggingContext 65535
typedef NS_ERROR_ENUM(GCDAsyncSocketErrorDomain, GCDAsyncSocketError) {
GCDAsyncSocketNoError = 0, // Never used
GCDAsyncSocketBadConfigError, // Invalid configuration
GCDAsyncSocketBadParamError, // Invalid parameter was passed
GCDAsyncSocketConnectTimeoutError, // A connect operation timed out
GCDAsyncSocketReadTimeoutError, // A read operation timed out
GCDAsyncSocketWriteTimeoutError, // A write operation timed out
GCDAsyncSocketReadMaxedOutError, // Reached set maxLength without completing
GCDAsyncSocketClosedError, // The remote peer closed the connection
GCDAsyncSocketOtherError, // Description provided in userInfo
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface GCDAsyncSocket : NSObject
/**
* GCDAsyncSocket uses the standard delegate paradigm,
* but executes all delegate callbacks on a given delegate dispatch queue.
* This allows for maximum concurrency, while at the same time providing easy thread safety.
*
* You MUST set a delegate AND delegate dispatch queue before attempting to
* use the socket, or you will get an error.
*
* The socket queue is optional.
* If you pass NULL, GCDAsyncSocket will automatically create it's own socket queue.
* If you choose to provide a socket queue, the socket queue must not be a concurrent queue.
* If you choose to provide a socket queue, and the socket queue has a configured target queue,
* then please see the discussion for the method markSocketQueueTargetQueue.
*
* The delegate queue and socket queue can optionally be the same.
**/
- (instancetype)init;
- (instancetype)initWithSocketQueue:(nullable dispatch_queue_t)sq;
- (instancetype)initWithDelegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq;
- (instancetype)initWithDelegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq NS_DESIGNATED_INITIALIZER;
/**
* Create GCDAsyncSocket from already connect BSD socket file descriptor
**/
+ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD socketQueue:(nullable dispatch_queue_t)sq error:(NSError**)error;
+ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq error:(NSError**)error;
+ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq error:(NSError **)error;
#pragma mark Configuration
@property (atomic, weak, readwrite, nullable) id delegate;
#if OS_OBJECT_USE_OBJC
@property (atomic, strong, readwrite, nullable) dispatch_queue_t delegateQueue;
#else
@property (atomic, assign, readwrite, nullable) dispatch_queue_t delegateQueue;
#endif
- (void)getDelegate:(id __nullable * __nullable)delegatePtr delegateQueue:(dispatch_queue_t __nullable * __nullable)delegateQueuePtr;
- (void)setDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue;
/**
* If you are setting the delegate to nil within the delegate's dealloc method,
* you may need to use the synchronous versions below.
**/
- (void)synchronouslySetDelegate:(nullable id)delegate;
- (void)synchronouslySetDelegateQueue:(nullable dispatch_queue_t)delegateQueue;
- (void)synchronouslySetDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue;
/**
* By default, both IPv4 and IPv6 are enabled.
*
* For accepting incoming connections, this means GCDAsyncSocket automatically supports both protocols,
* and can simulataneously accept incoming connections on either protocol.
*
* For outgoing connections, this means GCDAsyncSocket can connect to remote hosts running either protocol.
* If a DNS lookup returns only IPv4 results, GCDAsyncSocket will automatically use IPv4.
* If a DNS lookup returns only IPv6 results, GCDAsyncSocket will automatically use IPv6.
* If a DNS lookup returns both IPv4 and IPv6 results, the preferred protocol will be chosen.
* By default, the preferred protocol is IPv4, but may be configured as desired.
**/
@property (atomic, assign, readwrite, getter=isIPv4Enabled) BOOL IPv4Enabled;
@property (atomic, assign, readwrite, getter=isIPv6Enabled) BOOL IPv6Enabled;
@property (atomic, assign, readwrite, getter=isIPv4PreferredOverIPv6) BOOL IPv4PreferredOverIPv6;
/**
* When connecting to both IPv4 and IPv6 using Happy Eyeballs (RFC 6555) https://tools.ietf.org/html/rfc6555
* this is the delay between connecting to the preferred protocol and the fallback protocol.
*
* Defaults to 300ms.
**/
@property (atomic, assign, readwrite) NSTimeInterval alternateAddressDelay;
/**
* User data allows you to associate arbitrary information with the socket.
* This data is not used internally by socket in any way.
**/
@property (atomic, strong, readwrite, nullable) id userData;
#pragma mark Accepting
/**
* Tells the socket to begin listening and accepting connections on the given port.
* When a connection is accepted, a new instance of GCDAsyncSocket will be spawned to handle it,
* and the socket:didAcceptNewSocket: delegate method will be invoked.
*
* The socket will listen on all available interfaces (e.g. wifi, ethernet, etc)
**/
- (BOOL)acceptOnPort:(uint16_t)port error:(NSError **)errPtr;
/**
* This method is the same as acceptOnPort:error: with the
* additional option of specifying which interface to listen on.
*
* For example, you could specify that the socket should only accept connections over ethernet,
* and not other interfaces such as wifi.
*
* The interface may be specified by name (e.g. "en1" or "lo0") or by IP address (e.g. "192.168.4.34").
* You may also use the special strings "localhost" or "loopback" to specify that
* the socket only accept connections from the local machine.
*
* You can see the list of interfaces via the command line utility "ifconfig",
* or programmatically via the getifaddrs() function.
*
* To accept connections on any interface pass nil, or simply use the acceptOnPort:error: method.
**/
- (BOOL)acceptOnInterface:(nullable NSString *)interface port:(uint16_t)port error:(NSError **)errPtr;
/**
* Tells the socket to begin listening and accepting connections on the unix domain at the given url.
* When a connection is accepted, a new instance of GCDAsyncSocket will be spawned to handle it,
* and the socket:didAcceptNewSocket: delegate method will be invoked.
*
* The socket will listen on all available interfaces (e.g. wifi, ethernet, etc)
**/
- (BOOL)acceptOnUrl:(NSURL *)url error:(NSError **)errPtr;
#pragma mark Connecting
/**
* Connects to the given host and port.
*
* This method invokes connectToHost:onPort:viaInterface:withTimeout:error:
* and uses the default interface, and no timeout.
**/
- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr;
/**
* Connects to the given host and port with an optional timeout.
*
* This method invokes connectToHost:onPort:viaInterface:withTimeout:error: and uses the default interface.
**/
- (BOOL)connectToHost:(NSString *)host
onPort:(uint16_t)port
withTimeout:(NSTimeInterval)timeout
error:(NSError **)errPtr;
/**
* Connects to the given host & port, via the optional interface, with an optional timeout.
*
* The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2").
* The host may also be the special strings "localhost" or "loopback" to specify connecting
* to a service on the local machine.
*
* The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35").
* The interface may also be used to specify the local port (see below).
*
* To not time out use a negative time interval.
*
* This method will return NO if an error is detected, and set the error pointer (if one was given).
* Possible errors would be a nil host, invalid interface, or socket is already connected.
*
* If no errors are detected, this method will start a background connect operation and immediately return YES.
* The delegate callbacks are used to notify you when the socket connects, or if the host was unreachable.
*
* Since this class supports queued reads and writes, you can immediately start reading and/or writing.
* All read/write operations will be queued, and upon socket connection,
* the operations will be dequeued and processed in order.
*
* The interface may optionally contain a port number at the end of the string, separated by a colon.
* This allows you to specify the local port that should be used for the outgoing connection. (read paragraph to end)
* To specify both interface and local port: "en1:8082" or "192.168.4.35:2424".
* To specify only local port: ":8082".
* Please note this is an advanced feature, and is somewhat hidden on purpose.
* You should understand that 99.999% of the time you should NOT specify the local port for an outgoing connection.
* If you think you need to, there is a very good chance you have a fundamental misunderstanding somewhere.
* Local ports do NOT need to match remote ports. In fact, they almost never do.
* This feature is here for networking professionals using very advanced techniques.
**/
- (BOOL)connectToHost:(NSString *)host
onPort:(uint16_t)port
viaInterface:(nullable NSString *)interface
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];
*
* This method invokes connectToAddress:remoteAddr viaInterface:nil withTimeout:-1 error:errPtr.
**/
- (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;
/**
* Connects to the given address, using the specified interface and timeout.
*
* The address is 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];
*
* The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35").
* The interface may also be used to specify the local port (see below).
*
* The timeout is optional. To not time out use a negative time interval.
*
* This method will return NO if an error is detected, and set the error pointer (if one was given).
* Possible errors would be a nil host, invalid interface, or socket is already connected.
*
* If no errors are detected, this method will start a background connect operation and immediately return YES.
* The delegate callbacks are used to notify you when the socket connects, or if the host was unreachable.
*
* Since this class supports queued reads and writes, you can immediately start reading and/or writing.
* All read/write operations will be queued, and upon socket connection,
* the operations will be dequeued and processed in order.
*
* The interface may optionally contain a port number at the end of the string, separated by a colon.
* This allows you to specify the local port that should be used for the outgoing connection. (read paragraph to end)
* To specify both interface and local port: "en1:8082" or "192.168.4.35:2424".
* To specify only local port: ":8082".
* Please note this is an advanced feature, and is somewhat hidden on purpose.
* You should understand that 99.999% of the time you should NOT specify the local port for an outgoing connection.
* If you think you need to, there is a very good chance you have a fundamental misunderstanding somewhere.
* Local ports do NOT need to match remote ports. In fact, they almost never do.
* This feature is here for networking professionals using very advanced techniques.
**/
- (BOOL)connectToAddress:(NSData *)remoteAddr
viaInterface:(nullable NSString *)interface
withTimeout:(NSTimeInterval)timeout
error:(NSError **)errPtr;
/**
* Connects to the unix domain socket at the given url, using the specified timeout.
*/
- (BOOL)connectToUrl:(NSURL *)url withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr;
/**
* Iterates over the given NetService's addresses in order, and invokes connectToAddress:error:. Stops at the
* first invocation that succeeds and returns YES; otherwise returns NO.
*/
- (BOOL)connectToNetService:(NSNetService *)netService error:(NSError **)errPtr;
#pragma mark Disconnecting
/**
* Disconnects immediately (synchronously). Any pending reads or writes are dropped.
*
* If the socket is not already disconnected, an invocation to the socketDidDisconnect:withError: delegate method
* will be queued onto the delegateQueue asynchronously (behind any previously queued delegate methods).
* In other words, the disconnected delegate method will be invoked sometime shortly after this method returns.
*
* Please note the recommended way of releasing a GCDAsyncSocket instance (e.g. in a dealloc method)
* [asyncSocket setDelegate:nil];
* [asyncSocket disconnect];
* [asyncSocket release];
*
* If you plan on disconnecting the socket, and then immediately asking it to connect again,
* you'll likely want to do so like this:
* [asyncSocket setDelegate:nil];
* [asyncSocket disconnect];
* [asyncSocket setDelegate:self];
* [asyncSocket connect...];
**/
- (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;
#pragma mark Diagnostics
/**
* Returns whether the socket is disconnected or connected.
*
* A disconnected socket may be recycled.
* That is, it can be used again for connecting or listening.
*
* If a socket is in the process of connecting, it may be neither disconnected nor connected.
**/
@property (atomic, readonly) BOOL isDisconnected;
@property (atomic, readonly) 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.
**/
@property (atomic, readonly, nullable) NSString *connectedHost;
@property (atomic, readonly) uint16_t connectedPort;
@property (atomic, readonly, nullable) NSURL *connectedUrl;
@property (atomic, readonly, nullable) NSString *localHost;
@property (atomic, readonly) uint16_t localPort;
/**
* Returns the local or remote address to which this socket is connected,
* specified as a sockaddr structure wrapped in a NSData object.
*
* @seealso connectedHost
* @seealso connectedPort
* @seealso localHost
* @seealso localPort
**/
@property (atomic, readonly, nullable) NSData *connectedAddress;
@property (atomic, readonly, nullable) NSData *localAddress;
/**
* Returns whether the socket is IPv4 or IPv6.
* An accepting socket may be both.
**/
@property (atomic, readonly) BOOL isIPv4;
@property (atomic, readonly) BOOL isIPv6;
/**
* Returns whether or not the socket has been secured via SSL/TLS.
*
* See also the startTLS method.
**/
@property (atomic, readonly) BOOL isSecure;
#pragma mark Reading
// The readData and writeData methods won't block (they are asynchronous).
//
// When a read is complete the socket:didReadData:withTag: delegate method is dispatched on the delegateQueue.
// When a write is complete the socket:didWriteDataWithTag: delegate method is dispatched on the delegateQueue.
//
// 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 "socket:shouldTimeout..." delegate method
// is called to optionally allow you to extend the timeout.
// Upon a timeout, the "socket:didDisconnectWithError:" method is called
//
// 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 is 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 the socket is using it.
* After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
* That is, it will reference the bytes that were appended to the given buffer via
* the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
**/
- (void)readDataWithTimeout:(NSTimeInterval)timeout
buffer:(nullable 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 is 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 the socket is using it.
* After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
* That is, it will reference the bytes that were appended to the given buffer via
* the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
**/
- (void)readDataWithTimeout:(NSTimeInterval)timeout
buffer:(nullable 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 is 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 socket:didReadData:withTag: will be a subset of the given buffer.
* That is, it will reference the bytes that were appended to the given buffer via
* the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
**/
- (void)readDataToLength:(NSUInteger)length
withTimeout:(NSTimeInterval)timeout
buffer:(nullable 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 (except maybe print a warning), 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.
* If you're developing your own custom protocol, be sure your separator can not occur naturally as
* part of the data between separators.
* For example, imagine you want to send several small documents over a socket.
* Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents.
* In this particular example, it would be better to use a protocol similar to HTTP with
* a header that includes the length of the document.
* Also be careful that your separator cannot occur naturally as part of the encoding for a character.
*
* The given data (separator) parameter should be immutable.
* For performance reasons, the socket will retain it, not copy it.
* So if it is immutable, don't modify it while the socket is using it.
**/
- (void)readDataToData:(nullable 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 is 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 (except maybe print a warning), and the delegate will not be called.
*
* If you pass a buffer, you must not alter it in any way while the socket is using it.
* After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
* That is, it will reference the bytes that were appended to the given buffer via
* the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
*
* To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
* If you're developing your own custom protocol, be sure your separator can not occur naturally as
* part of the data between separators.
* For example, imagine you want to send several small documents over a socket.
* Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents.
* In this particular example, it would be better to use a protocol similar to HTTP with
* a header that includes the length of the document.
* Also be careful that your separator cannot occur naturally as part of the encoding for a character.
*
* The given data (separator) parameter should be immutable.
* For performance reasons, the socket will retain it, not copy it.
* So if it is immutable, don't modify it while the socket is using it.
**/
- (void)readDataToData:(NSData *)data
withTimeout:(NSTimeInterval)timeout
buffer:(nullable 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 GCDAsyncSocketReadMaxedOutError.
* 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 (except maybe print a warning), 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 (except maybe print a warning), 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.
* If you're developing your own custom protocol, be sure your separator can not occur naturally as
* part of the data between separators.
* For example, imagine you want to send several small documents over a socket.
* Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents.
* In this particular example, it would be better to use a protocol similar to HTTP with
* a header that includes the length of the document.
* Also be careful that your separator cannot occur naturally as part of the encoding for a character.
*
* The given data (separator) parameter should be immutable.
* For performance reasons, the socket will retain it, not copy it.
* So if it is immutable, don't modify it while the socket is using it.
**/
- (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.
*
* If the timeout value is negative, the read operation will not use a timeout.
* If the buffer is 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 GCDAsyncSocketReadMaxedOutError.
* 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 (separator) parameter,
* the method will do nothing (except maybe print a warning), and the delegate will not be called.
* If the bufferOffset is greater than the length of the given buffer,
* the method will do nothing (except maybe print a warning), and the delegate will not be called.
*
* If you pass a buffer, you must not alter it in any way while the socket is using it.
* After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
* That is, it will reference the bytes that were appended to the given buffer via
* the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
*
* To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
* If you're developing your own custom protocol, be sure your separator can not occur naturally as
* part of the data between separators.
* For example, imagine you want to send several small documents over a socket.
* Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents.
* In this particular example, it would be better to use a protocol similar to HTTP with
* a header that includes the length of the document.
* Also be careful that your separator cannot occur naturally as part of the encoding for a character.
*
* The given data (separator) parameter should be immutable.
* For performance reasons, the socket will retain it, not copy it.
* So if it is immutable, don't modify it while the socket is using it.
**/
- (void)readDataToData:(NSData *)data
withTimeout:(NSTimeInterval)timeout
buffer:(nullable NSMutableData *)buffer
bufferOffset:(NSUInteger)offset
maxLength:(NSUInteger)length
tag:(long)tag;
/**
* Returns progress of the current read, from 0.0 to 1.0, or NaN if no current read (use isnan() to check).
* The parameters "tag", "done" and "total" will be filled in if they aren't NULL.
**/
- (float)progressOfReadReturningTag:(nullable long *)tagPtr bytesDone:(nullable NSUInteger *)donePtr total:(nullable NSUInteger *)totalPtr;
#pragma mark Writing
/**
* 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.
*
* Thread-Safety Note:
* If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while
* the socket is writing it. In other words, it's not safe to alter the data until after the delegate method
* socket:didWriteDataWithTag: is invoked signifying that this particular write operation has completed.
* This is due to the fact that GCDAsyncSocket does NOT copy the data. It simply retains it.
* This is for performance reasons. Often times, if NSMutableData is passed, it is because
* a request/response was built up in memory. Copying this data adds an unwanted/unneeded overhead.
* If you need to write data from an immutable buffer, and you need to alter the buffer before the socket
* completes writing the bytes (which is NOT immediately after this method returns, but rather at a later time
* when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method.
**/
- (void)writeData:(nullable NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
/**
* Returns progress of the current write, from 0.0 to 1.0, or NaN if no current write (use isnan() to check).
* The parameters "tag", "done" and "total" will be filled in if they aren't NULL.
**/
- (float)progressOfWriteReturningTag:(nullable long *)tagPtr bytesDone:(nullable NSUInteger *)donePtr total:(nullable NSUInteger *)totalPtr;
#pragma mark Security
/**
* 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 available TOP-LEVEL KEYS are:
*
* - GCDAsyncSocketManuallyEvaluateTrust
* The value must be of type NSNumber, encapsulating a BOOL value.
* If you set this to YES, then the underlying SecureTransport system will not evaluate the SecTrustRef of the peer.
* Instead it will pause at the moment evaulation would typically occur,
* and allow us to handle the security evaluation however we see fit.
* So GCDAsyncSocket will invoke the delegate method socket:shouldTrustPeer: passing the SecTrustRef.
*
* Note that if you set this option, then all other configuration keys are ignored.
* Evaluation will be completely up to you during the socket:didReceiveTrust:completionHandler: delegate method.
*
* For more information on trust evaluation see:
* Apple's Technical Note TN2232 - HTTPS Server Trust Evaluation
* https://developer.apple.com/library/ios/technotes/tn2232/_index.html
*
* If unspecified, the default value is NO.
*
* - GCDAsyncSocketUseCFStreamForTLS (iOS only)
* The value must be of type NSNumber, encapsulating a BOOL value.
* By default GCDAsyncSocket will use the SecureTransport layer to perform encryption.
* This gives us more control over the security protocol (many more configuration options),
* plus it allows us to optimize things like sys calls and buffer allocation.
*
* However, if you absolutely must, you can instruct GCDAsyncSocket to use the old-fashioned encryption
* technique by going through the CFStream instead. So instead of using SecureTransport, GCDAsyncSocket
* will instead setup a CFRead/CFWriteStream. And then set the kCFStreamPropertySSLSettings property
* (via CFReadStreamSetProperty / CFWriteStreamSetProperty) and will pass the given options to this method.
*
* Thus all the other keys in the given dictionary will be ignored by GCDAsyncSocket,
* and will passed directly CFReadStreamSetProperty / CFWriteStreamSetProperty.
* For more infomation on these keys, please see the documentation for kCFStreamPropertySSLSettings.
*
* If unspecified, the default value is NO.
*
* ==== The available CONFIGURATION KEYS are:
*
* - kCFStreamSSLPeerName
* The value must be of type NSString.
* It should match the name in the X.509 certificate given by the remote party.
* See Apple's documentation for SSLSetPeerDomainName.
*
* - kCFStreamSSLCertificates
* The value must be of type NSArray.
* See Apple's documentation for SSLSetCertificate.
*
* - kCFStreamSSLIsServer
* The value must be of type NSNumber, encapsulationg a BOOL value.
* See Apple's documentation for SSLCreateContext for iOS.
* This is optional for iOS. If not supplied, a NO value is the default.
* This is not needed for Mac OS X, and the value is ignored.
*
* - GCDAsyncSocketSSLPeerID
* The value must be of type NSData.
* You must set this value if you want to use TLS session resumption.
* See Apple's documentation for SSLSetPeerID.
*
* - GCDAsyncSocketSSLProtocolVersionMin
* - GCDAsyncSocketSSLProtocolVersionMax
* The value(s) must be of type NSNumber, encapsulting a SSLProtocol value.
* See Apple's documentation for SSLSetProtocolVersionMin & SSLSetProtocolVersionMax.
* See also the SSLProtocol typedef.
*
* - GCDAsyncSocketSSLSessionOptionFalseStart
* The value must be of type NSNumber, encapsulating a BOOL value.
* See Apple's documentation for kSSLSessionOptionFalseStart.
*
* - GCDAsyncSocketSSLSessionOptionSendOneByteRecord
* The value must be of type NSNumber, encapsulating a BOOL value.
* See Apple's documentation for kSSLSessionOptionSendOneByteRecord.
*
* - GCDAsyncSocketSSLCipherSuites
* The values must be of type NSArray.
* Each item within the array must be a NSNumber, encapsulating an SSLCipherSuite.
* See Apple's documentation for SSLSetEnabledCiphers.
* See also the SSLCipherSuite typedef.
*
* - GCDAsyncSocketSSLDiffieHellmanParameters (Mac OS X only)
* The value must be of type NSData.
* See Apple's documentation for SSLSetDiffieHellmanParams.
*
* ==== The following UNAVAILABLE KEYS are: (with throw an exception)
*
* - kCFStreamSSLAllowsAnyRoot (UNAVAILABLE)
* You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust).
* Corresponding deprecated method: SSLSetAllowsAnyRoot
*
* - kCFStreamSSLAllowsExpiredRoots (UNAVAILABLE)
* You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust).
* Corresponding deprecated method: SSLSetAllowsExpiredRoots
*
* - kCFStreamSSLAllowsExpiredCertificates (UNAVAILABLE)
* You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust).
* Corresponding deprecated method: SSLSetAllowsExpiredCerts
*
* - kCFStreamSSLValidatesCertificateChain (UNAVAILABLE)
* You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust).
* Corresponding deprecated method: SSLSetEnableCertVerify
*
* - kCFStreamSSLLevel (UNAVAILABLE)
* You MUST use GCDAsyncSocketSSLProtocolVersionMin & GCDAsyncSocketSSLProtocolVersionMin instead.
* Corresponding deprecated method: SSLSetProtocolVersionEnabled
*
*
* Please refer to Apple's documentation for corresponding SSLFunctions.
*
* If you pass in nil or an empty dictionary, the default settings will be used.
*
* IMPORTANT SECURITY NOTE:
* 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".
*
* You can also perform additional validation in socketDidSecure.
**/
- (void)startTLS:(nullable NSDictionary *)tlsSettings;
#pragma mark Advanced
/**
* Traditionally sockets are not closed until the conversation is over.
* However, it is technically possible for the remote enpoint to close its write stream.
* Our socket would then be notified that there is no more data to be read,
* but our socket would still be writeable and the remote endpoint could continue to receive our data.
*
* The argument for this confusing functionality stems from the idea that a client could shut down its
* write stream after sending a request to the server, thus notifying the server there are to be no further requests.
* In practice, however, this technique did little to help server developers.
*
* To make matters worse, from a TCP perspective there is no way to tell the difference from a read stream close
* and a full socket close. They both result in the TCP stack receiving a FIN packet. The only way to tell
* is by continuing to write to the socket. If it was only a read stream close, then writes will continue to work.
* Otherwise an error will be occur shortly (when the remote end sends us a RST packet).
*
* In addition to the technical challenges and confusion, many high level socket/stream API's provide
* no support for dealing with the problem. If the read stream is closed, the API immediately declares the
* socket to be closed, and shuts down the write stream as well. In fact, this is what Apple's CFStream API does.
* It might sound like poor design at first, but in fact it simplifies development.
*
* The vast majority of the time if the read stream is closed it's because the remote endpoint closed its socket.
* Thus it actually makes sense to close the socket at this point.
* And in fact this is what most networking developers want and expect to happen.
* However, if you are writing a server that interacts with a plethora of clients,
* you might encounter a client that uses the discouraged technique of shutting down its write stream.
* If this is the case, you can set this property to NO,
* and make use of the socketDidCloseReadStream delegate method.
*
* The default value is YES.
**/
@property (atomic, assign, readwrite) BOOL autoDisconnectOnClosedReadStream;
/**
* GCDAsyncSocket maintains thread safety by using an internal serial dispatch_queue.
* In most cases, the instance creates this queue itself.
* However, to allow for maximum flexibility, the internal queue may be passed in the init method.
* This allows for some advanced options such as controlling socket priority via target queues.
* However, when one begins to use target queues like this, they open the door to some specific deadlock issues.
*
* For example, imagine there are 2 queues:
* dispatch_queue_t socketQueue;
* dispatch_queue_t socketTargetQueue;
*
* If you do this (pseudo-code):
* socketQueue.targetQueue = socketTargetQueue;
*
* Then all socketQueue operations will actually get run on the given socketTargetQueue.
* This is fine and works great in most situations.
* But if you run code directly from within the socketTargetQueue that accesses the socket,
* you could potentially get deadlock. Imagine the following code:
*
* - (BOOL)socketHasSomething
* {
* __block BOOL result = NO;
* dispatch_block_t block = ^{
* result = [self someInternalMethodToBeRunOnlyOnSocketQueue];
* }
* if (is_executing_on_queue(socketQueue))
* block();
* else
* dispatch_sync(socketQueue, block);
*
* return result;
* }
*
* What happens if you call this method from the socketTargetQueue? The result is deadlock.
* This is because the GCD API offers no mechanism to discover a queue's targetQueue.
* Thus we have no idea if our socketQueue is configured with a targetQueue.
* If we had this information, we could easily avoid deadlock.
* But, since these API's are missing or unfeasible, you'll have to explicitly set it.
*
* IF you pass a socketQueue via the init method,
* AND you've configured the passed socketQueue with a targetQueue,
* THEN you should pass the end queue in the target hierarchy.
*
* For example, consider the following queue hierarchy:
* socketQueue -> ipQueue -> moduleQueue
*
* This example demonstrates priority shaping within some server.
* All incoming client connections from the same IP address are executed on the same target queue.
* And all connections for a particular module are executed on the same target queue.
* Thus, the priority of all networking for the entire module can be changed on the fly.
* Additionally, networking traffic from a single IP cannot monopolize the module.
*
* Here's how you would accomplish something like that:
* - (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock
* {
* dispatch_queue_t socketQueue = dispatch_queue_create("", NULL);
* dispatch_queue_t ipQueue = [self ipQueueForAddress:address];
*
* dispatch_set_target_queue(socketQueue, ipQueue);
* dispatch_set_target_queue(iqQueue, moduleQueue);
*
* return socketQueue;
* }
* - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
* {
* [clientConnections addObject:newSocket];
* [newSocket markSocketQueueTargetQueue:moduleQueue];
* }
*
* Note: This workaround is ONLY needed if you intend to execute code directly on the ipQueue or moduleQueue.
* This is often NOT the case, as such queues are used solely for execution shaping.
**/
- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreConfiguredTargetQueue;
- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreviouslyConfiguredTargetQueue;
/**
* It's not thread-safe to access certain variables from outside the socket's internal queue.
*
* For example, the socket file descriptor.
* File descriptors are simply integers which reference an index in the per-process file table.
* However, when one requests a new file descriptor (by opening a file or socket),
* the file descriptor returned is guaranteed to be the lowest numbered unused descriptor.
* So if we're not careful, the following could be possible:
*
* - Thread A invokes a method which returns the socket's file descriptor.
* - The socket is closed via the socket's internal queue on thread B.
* - Thread C opens a file, and subsequently receives the file descriptor that was previously the socket's FD.
* - Thread A is now accessing/altering the file instead of the socket.
*
* In addition to this, other variables are not actually objects,
* and thus cannot be retained/released or even autoreleased.
* An example is the sslContext, of type SSLContextRef, which is actually a malloc'd struct.
*
* Although there are internal variables that make it difficult to maintain thread-safety,
* it is important to provide access to these variables
* to ensure this class can be used in a wide array of environments.
* This method helps to accomplish this by invoking the current block on the socket's internal queue.
* The methods below can be invoked from within the block to access
* those generally thread-unsafe internal variables in a thread-safe manner.
* The given block will be invoked synchronously on the socket's internal queue.
*
* If you save references to any protected variables and use them outside the block, you do so at your own peril.
**/
- (void)performBlock:(dispatch_block_t)block;
/**
* These methods are only available from within the context of a performBlock: invocation.
* See the documentation for the performBlock: method above.
*
* Provides access to the socket's file descriptor(s).
* If the socket is a server socket (is accepting incoming connections),
* it might actually have multiple internal socket file descriptors - one for IPv4 and one for IPv6.
**/
- (int)socketFD;
- (int)socket4FD;
- (int)socket6FD;
#if TARGET_OS_IPHONE
/**
* These methods are only available from within the context of a performBlock: invocation.
* See the documentation for the performBlock: method above.
*
* Provides access to the socket's internal CFReadStream/CFWriteStream.
*
* These streams are only used as workarounds for specific iOS shortcomings:
*
* - Apple has decided to keep the SecureTransport framework private is iOS.
* This means the only supplied way to do SSL/TLS is via CFStream or some other API layered on top of it.
* Thus, in order to provide SSL/TLS support on iOS we are forced to rely on CFStream,
* instead of the preferred and faster and more powerful SecureTransport.
*
* - If a socket doesn't have backgrounding enabled, and that socket is closed while the app is backgrounded,
* Apple only bothers to notify us via the CFStream API.
* The faster and more powerful GCD API isn't notified properly in this case.
*
* See also: (BOOL)enableBackgroundingOnSocket
**/
- (nullable CFReadStreamRef)readStream;
- (nullable CFWriteStreamRef)writeStream;
/**
* This method is only available from within the context of a performBlock: invocation.
* See the documentation for the performBlock: method above.
*
* Configures the socket to allow it to operate when the iOS application has been backgrounded.
* In other words, this method creates a read & write stream, and invokes:
*
* CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
* CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
*
* Returns YES if successful, NO otherwise.
*
* Note: Apple does not officially support backgrounding server sockets.
* That is, if your socket is accepting incoming connections, Apple does not officially support
* allowing iOS applications to accept incoming connections while an app is backgrounded.
*
* Example usage:
*
* - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
* {
* [asyncSocket performBlock:^{
* [asyncSocket enableBackgroundingOnSocket];
* }];
* }
**/
- (BOOL)enableBackgroundingOnSocket;
#endif
/**
* This method is only available from within the context of a performBlock: invocation.
* See the documentation for the performBlock: method above.
*
* Provides access to the socket's SSLContext, if SSL/TLS has been started on the socket.
**/
- (nullable SSLContextRef)sslContext;
#pragma mark Utilities
/**
* The address lookup utility used by the class.
* This method is synchronous, so it's recommended you use it on a background thread/queue.
*
* The special strings "localhost" and "loopback" return the loopback address for IPv4 and IPv6.
*
* @returns
* A mutable array with all IPv4 and IPv6 addresses returned by getaddrinfo.
* The addresses are specifically for TCP connections.
* You can filter the addresses, if needed, using the other utility methods provided by the class.
**/
+ (nullable NSMutableArray *)lookupHost:(NSString *)host port:(uint16_t)port error:(NSError **)errPtr;
/**
* Extracting host and port information from raw address data.
**/
+ (nullable NSString *)hostFromAddress:(NSData *)address;
+ (uint16_t)portFromAddress:(NSData *)address;
+ (BOOL)isIPv4Address:(NSData *)address;
+ (BOOL)isIPv6Address:(NSData *)address;
+ (BOOL)getHost:( NSString * __nullable * __nullable)hostPtr port:(nullable uint16_t *)portPtr fromAddress:(NSData *)address;
+ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(nullable uint16_t *)portPtr family:(nullable sa_family_t *)afPtr fromAddress:(NSData *)address;
/**
* A few common line separators, for use with the readDataToData:... methods.
**/
+ (NSData *)CRLFData; // 0x0D0A
+ (NSData *)CRData; // 0x0D
+ (NSData *)LFData; // 0x0A
+ (NSData *)ZeroData; // 0x00
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@protocol GCDAsyncSocketDelegate
@optional
/**
* This method is called immediately prior to socket:didAcceptNewSocket:.
* It optionally allows a listening socket to specify the socketQueue for a new accepted socket.
* If this method is not implemented, or returns NULL, the new accepted socket will create its own default queue.
*
* Since you cannot autorelease a dispatch_queue,
* this method uses the "new" prefix in its name to specify that the returned queue has been retained.
*
* Thus you could do something like this in the implementation:
* return dispatch_queue_create("MyQueue", NULL);
*
* If you are placing multiple sockets on the same queue,
* then care should be taken to increment the retain count each time this method is invoked.
*
* For example, your implementation might look something like this:
* dispatch_retain(myExistingQueue);
* return myExistingQueue;
**/
- (nullable dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock;
/**
* Called when a socket accepts a connection.
* Another socket is automatically spawned to handle it.
*
* You must retain the newSocket if you wish to handle the connection.
* Otherwise the newSocket instance will be released and the spawned connection will be closed.
*
* By default the new socket will have the same delegate and delegateQueue.
* You may, of course, change this at any time.
**/
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket;
/**
* 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)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port;
/**
* 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)socket:(GCDAsyncSocket *)sock didConnectToUrl:(NSURL *)url;
/**
* Called when a socket has completed reading the requested data into memory.
* Not called if there is an error.
**/
- (void)socket:(GCDAsyncSocket *)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 for things such as updating progress bars.
**/
- (void)socket:(GCDAsyncSocket *)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)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag;
/**
* Called when a socket has written some data, but has not yet completed the entire write.
* It may be used for things such as updating progress bars.
**/
- (void)socket:(GCDAsyncSocket *)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)socket:(GCDAsyncSocket *)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)socket:(GCDAsyncSocket *)sock shouldTimeoutWriteWithTag:(long)tag
elapsed:(NSTimeInterval)elapsed
bytesDone:(NSUInteger)length;
/**
* Conditionally called if the read stream closes, but the write stream may still be writeable.
*
* This delegate method is only called if autoDisconnectOnClosedReadStream has been set to NO.
* See the discussion on the autoDisconnectOnClosedReadStream method for more information.
**/
- (void)socketDidCloseReadStream:(GCDAsyncSocket *)sock;
/**
* Called when a socket disconnects with or without error.
*
* If you call the disconnect method, and the socket wasn't already disconnected,
* then an invocation of this delegate method will be enqueued on the delegateQueue
* before the disconnect method returns.
*
* Note: If the GCDAsyncSocket instance is deallocated while it is still connected,
* and the delegate is not also deallocated, then this method will be invoked,
* but the sock parameter will be nil. (It must necessarily be nil since it is no longer available.)
* This is a generally rare, but is possible if one writes code like this:
*
* asyncSocket = nil; // I'm implicitly disconnecting the socket
*
* In this case it may preferrable to nil the delegate beforehand, like this:
*
* asyncSocket.delegate = nil; // Don't invoke my delegate method
* asyncSocket = nil; // I'm implicitly disconnecting the socket
*
* Of course, this depends on how your state machine is configured.
**/
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(nullable NSError *)err;
/**
* 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 socketDidDisconnect:withError: delegate method will be called with the specific SSL error code.
**/
- (void)socketDidSecure:(GCDAsyncSocket *)sock;
/**
* Allows a socket delegate to hook into the TLS handshake and manually validate the peer it's connecting to.
*
* This is only called if startTLS is invoked with options that include:
* - GCDAsyncSocketManuallyEvaluateTrust == YES
*
* Typically the delegate will use SecTrustEvaluate (and related functions) to properly validate the peer.
*
* Note from Apple's documentation:
* Because [SecTrustEvaluate] might look on the network for certificates in the certificate chain,
* [it] might block while attempting network access. You should never call it from your main thread;
* call it only from within a function running on a dispatch queue or on a separate thread.
*
* Thus this method uses a completionHandler block rather than a normal return value.
* The completionHandler block is thread-safe, and may be invoked from a background queue/thread.
* It is safe to invoke the completionHandler block even if the socket has been closed.
**/
- (void)socket:(GCDAsyncSocket *)sock didReceiveTrust:(SecTrustRef)trust
completionHandler:(void (^)(BOOL shouldTrustPeer))completionHandler;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncSocket.m
================================================
//
// GCDAsyncSocket.m
//
// This class is in the public domain.
// Originally created by Robbie Hanson in Q4 2010.
// Updated and maintained by Deusty LLC and the Apple development community.
//
// https://github.com/robbiehanson/CocoaAsyncSocket
//
#import "GCDAsyncSocket.h"
#if TARGET_OS_IPHONE
#import
#endif
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
// For more information see: https://github.com/robbiehanson/CocoaAsyncSocket/wiki/ARC
#endif
#ifndef GCDAsyncSocketLoggingEnabled
#define GCDAsyncSocketLoggingEnabled 0
#endif
#if GCDAsyncSocketLoggingEnabled
// Logging Enabled - See log level below
// Logging uses the CocoaLumberjack framework (which is also GCD based).
// https://github.com/robbiehanson/CocoaLumberjack
//
// It allows us to do a lot of logging without significantly slowing down the code.
#import "DDLog.h"
#define LogAsync YES
#define LogContext GCDAsyncSocketLoggingContext
#define LogObjc(flg, frmt, ...) LOG_OBJC_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__)
#define LogC(flg, frmt, ...) LOG_C_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__)
#define LogError(frmt, ...) LogObjc(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogWarn(frmt, ...) LogObjc(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogInfo(frmt, ...) LogObjc(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogVerbose(frmt, ...) LogObjc(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogCError(frmt, ...) LogC(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogCWarn(frmt, ...) LogC(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogCInfo(frmt, ...) LogC(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogCVerbose(frmt, ...) LogC(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogTrace() LogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD)
#define LogCTrace() LogC(LOG_FLAG_VERBOSE, @"%@: %s", THIS_FILE, __FUNCTION__)
#ifndef GCDAsyncSocketLogLevel
#define GCDAsyncSocketLogLevel LOG_LEVEL_VERBOSE
#endif
// Log levels : off, error, warn, info, verbose
static const int logLevel = GCDAsyncSocketLogLevel;
#else
// Logging Disabled
#define LogError(frmt, ...) {}
#define LogWarn(frmt, ...) {}
#define LogInfo(frmt, ...) {}
#define LogVerbose(frmt, ...) {}
#define LogCError(frmt, ...) {}
#define LogCWarn(frmt, ...) {}
#define LogCInfo(frmt, ...) {}
#define LogCVerbose(frmt, ...) {}
#define LogTrace() {}
#define LogCTrace(frmt, ...) {}
#endif
/**
* Seeing a return statements within an inner block
* can sometimes be mistaken for a return point of the enclosing method.
* This makes inline blocks a bit easier to read.
**/
#define return_from_block return
/**
* A socket file descriptor is really just an integer.
* It represents the index of the socket within the kernel.
* This makes invalid file descriptor comparisons easier to read.
**/
#define SOCKET_NULL -1
NSString *const GCDAsyncSocketException = @"GCDAsyncSocketException";
NSString *const GCDAsyncSocketErrorDomain = @"GCDAsyncSocketErrorDomain";
NSString *const GCDAsyncSocketQueueName = @"GCDAsyncSocket";
NSString *const GCDAsyncSocketThreadName = @"GCDAsyncSocket-CFStream";
NSString *const GCDAsyncSocketManuallyEvaluateTrust = @"GCDAsyncSocketManuallyEvaluateTrust";
#if TARGET_OS_IPHONE
NSString *const GCDAsyncSocketUseCFStreamForTLS = @"GCDAsyncSocketUseCFStreamForTLS";
#endif
NSString *const GCDAsyncSocketSSLPeerID = @"GCDAsyncSocketSSLPeerID";
NSString *const GCDAsyncSocketSSLProtocolVersionMin = @"GCDAsyncSocketSSLProtocolVersionMin";
NSString *const GCDAsyncSocketSSLProtocolVersionMax = @"GCDAsyncSocketSSLProtocolVersionMax";
NSString *const GCDAsyncSocketSSLSessionOptionFalseStart = @"GCDAsyncSocketSSLSessionOptionFalseStart";
NSString *const GCDAsyncSocketSSLSessionOptionSendOneByteRecord = @"GCDAsyncSocketSSLSessionOptionSendOneByteRecord";
NSString *const GCDAsyncSocketSSLCipherSuites = @"GCDAsyncSocketSSLCipherSuites";
NSString *const GCDAsyncSocketSSLALPN = @"GCDAsyncSocketSSLALPN";
#if !TARGET_OS_IPHONE
NSString *const GCDAsyncSocketSSLDiffieHellmanParameters = @"GCDAsyncSocketSSLDiffieHellmanParameters";
#endif
enum GCDAsyncSocketFlags
{
kSocketStarted = 1 << 0, // If set, socket has been started (accepting/connecting)
kConnected = 1 << 1, // If set, the socket is connected
kForbidReadsWrites = 1 << 2, // If set, no new reads or writes are allowed
kReadsPaused = 1 << 3, // If set, reads are paused due to possible timeout
kWritesPaused = 1 << 4, // If set, writes are paused due to possible timeout
kDisconnectAfterReads = 1 << 5, // If set, disconnect after no more reads are queued
kDisconnectAfterWrites = 1 << 6, // If set, disconnect after no more writes are queued
kSocketCanAcceptBytes = 1 << 7, // If set, we know socket can accept bytes. If unset, it's unknown.
kReadSourceSuspended = 1 << 8, // If set, the read source is suspended
kWriteSourceSuspended = 1 << 9, // If set, the write source is suspended
kQueuedTLS = 1 << 10, // If set, we've queued an upgrade to TLS
kStartingReadTLS = 1 << 11, // If set, we're waiting for TLS negotiation to complete
kStartingWriteTLS = 1 << 12, // If set, we're waiting for TLS negotiation to complete
kSocketSecure = 1 << 13, // If set, socket is using secure communication via SSL/TLS
kSocketHasReadEOF = 1 << 14, // If set, we have read EOF from socket
kReadStreamClosed = 1 << 15, // If set, we've read EOF plus prebuffer has been drained
kDealloc = 1 << 16, // If set, the socket is being deallocated
#if TARGET_OS_IPHONE
kAddedStreamsToRunLoop = 1 << 17, // If set, CFStreams have been added to listener thread
kUsingCFStreamForTLS = 1 << 18, // If set, we're forced to use CFStream instead of SecureTransport
kSecureSocketHasBytesAvailable = 1 << 19, // If set, CFReadStream has notified us of bytes available
#endif
};
enum GCDAsyncSocketConfig
{
kIPv4Disabled = 1 << 0, // If set, IPv4 is disabled
kIPv6Disabled = 1 << 1, // If set, IPv6 is disabled
kPreferIPv6 = 1 << 2, // If set, IPv6 is preferred over IPv4
kAllowHalfDuplexConnection = 1 << 3, // If set, the socket will stay open even if the read stream closes
};
#if TARGET_OS_IPHONE
static NSThread *cfstreamThread; // Used for CFStreams
static uint64_t cfstreamThreadRetainCount; // setup & teardown
static dispatch_queue_t cfstreamThreadSetupQueue; // setup & teardown
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* A PreBuffer is used when there is more data available on the socket
* than is being requested by current read request.
* In this case we slurp up all data from the socket (to minimize sys calls),
* and store additional yet unread data in a "prebuffer".
*
* The prebuffer is entirely drained before we read from the socket again.
* In other words, a large chunk of data is written is written to the prebuffer.
* The prebuffer is then drained via a series of one or more reads (for subsequent read request(s)).
*
* A ring buffer was once used for this purpose.
* But a ring buffer takes up twice as much memory as needed (double the size for mirroring).
* In fact, it generally takes up more than twice the needed size as everything has to be rounded up to vm_page_size.
* And since the prebuffer is always completely drained after being written to, a full ring buffer isn't needed.
*
* The current design is very simple and straight-forward, while also keeping memory requirements lower.
**/
@interface GCDAsyncSocketPreBuffer : NSObject
{
uint8_t *preBuffer;
size_t preBufferSize;
uint8_t *readPointer;
uint8_t *writePointer;
}
- (instancetype)initWithCapacity:(size_t)numBytes NS_DESIGNATED_INITIALIZER;
- (void)ensureCapacityForWrite:(size_t)numBytes;
- (size_t)availableBytes;
- (uint8_t *)readBuffer;
- (void)getReadBuffer:(uint8_t **)bufferPtr availableBytes:(size_t *)availableBytesPtr;
- (size_t)availableSpace;
- (uint8_t *)writeBuffer;
- (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr;
- (void)didRead:(size_t)bytesRead;
- (void)didWrite:(size_t)bytesWritten;
- (void)reset;
@end
@implementation GCDAsyncSocketPreBuffer
// Cover the superclass' designated initializer
- (instancetype)init NS_UNAVAILABLE
{
NSAssert(0, @"Use the designated initializer");
return nil;
}
- (instancetype)initWithCapacity:(size_t)numBytes
{
if ((self = [super init]))
{
preBufferSize = numBytes;
preBuffer = malloc(preBufferSize);
readPointer = preBuffer;
writePointer = preBuffer;
}
return self;
}
- (void)dealloc
{
if (preBuffer)
free(preBuffer);
}
- (void)ensureCapacityForWrite:(size_t)numBytes
{
size_t availableSpace = [self availableSpace];
if (numBytes > availableSpace)
{
size_t additionalBytes = numBytes - availableSpace;
size_t newPreBufferSize = preBufferSize + additionalBytes;
uint8_t *newPreBuffer = realloc(preBuffer, newPreBufferSize);
size_t readPointerOffset = readPointer - preBuffer;
size_t writePointerOffset = writePointer - preBuffer;
preBuffer = newPreBuffer;
preBufferSize = newPreBufferSize;
readPointer = preBuffer + readPointerOffset;
writePointer = preBuffer + writePointerOffset;
}
}
- (size_t)availableBytes
{
return writePointer - readPointer;
}
- (uint8_t *)readBuffer
{
return readPointer;
}
- (void)getReadBuffer:(uint8_t **)bufferPtr availableBytes:(size_t *)availableBytesPtr
{
if (bufferPtr) *bufferPtr = readPointer;
if (availableBytesPtr) *availableBytesPtr = [self availableBytes];
}
- (void)didRead:(size_t)bytesRead
{
readPointer += bytesRead;
if (readPointer == writePointer)
{
// The prebuffer has been drained. Reset pointers.
readPointer = preBuffer;
writePointer = preBuffer;
}
}
- (size_t)availableSpace
{
return preBufferSize - (writePointer - preBuffer);
}
- (uint8_t *)writeBuffer
{
return writePointer;
}
- (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr
{
if (bufferPtr) *bufferPtr = writePointer;
if (availableSpacePtr) *availableSpacePtr = [self availableSpace];
}
- (void)didWrite:(size_t)bytesWritten
{
writePointer += bytesWritten;
}
- (void)reset
{
readPointer = preBuffer;
writePointer = preBuffer;
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* The GCDAsyncReadPacket encompasses the instructions for any given read.
* The content of a read packet allows the code to determine if we're:
* - reading to a certain length
* - reading to a certain separator
* - or simply reading the first chunk of available data
**/
@interface GCDAsyncReadPacket : NSObject
{
@public
NSMutableData *buffer;
NSUInteger startOffset;
NSUInteger bytesDone;
NSUInteger maxLength;
NSTimeInterval timeout;
NSUInteger readLength;
NSData *term;
BOOL bufferOwner;
NSUInteger originalBufferLength;
long tag;
}
- (instancetype)initWithData:(NSMutableData *)d
startOffset:(NSUInteger)s
maxLength:(NSUInteger)m
timeout:(NSTimeInterval)t
readLength:(NSUInteger)l
terminator:(NSData *)e
tag:(long)i NS_DESIGNATED_INITIALIZER;
- (void)ensureCapacityForAdditionalDataOfLength:(NSUInteger)bytesToRead;
- (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr;
- (NSUInteger)readLengthForNonTermWithHint:(NSUInteger)bytesAvailable;
- (NSUInteger)readLengthForTermWithHint:(NSUInteger)bytesAvailable shouldPreBuffer:(BOOL *)shouldPreBufferPtr;
- (NSUInteger)readLengthForTermWithPreBuffer:(GCDAsyncSocketPreBuffer *)preBuffer found:(BOOL *)foundPtr;
- (NSInteger)searchForTermAfterPreBuffering:(ssize_t)numBytes;
@end
@implementation GCDAsyncReadPacket
// Cover the superclass' designated initializer
- (instancetype)init NS_UNAVAILABLE
{
NSAssert(0, @"Use the designated initializer");
return nil;
}
- (instancetype)initWithData:(NSMutableData *)d
startOffset:(NSUInteger)s
maxLength:(NSUInteger)m
timeout:(NSTimeInterval)t
readLength:(NSUInteger)l
terminator:(NSData *)e
tag:(long)i
{
if((self = [super init]))
{
bytesDone = 0;
maxLength = m;
timeout = t;
readLength = l;
term = [e copy];
tag = i;
if (d)
{
buffer = d;
startOffset = s;
bufferOwner = NO;
originalBufferLength = [d length];
}
else
{
if (readLength > 0)
buffer = [[NSMutableData alloc] initWithLength:readLength];
else
buffer = [[NSMutableData alloc] initWithLength:0];
startOffset = 0;
bufferOwner = YES;
originalBufferLength = 0;
}
}
return self;
}
/**
* Increases the length of the buffer (if needed) to ensure a read of the given size will fit.
**/
- (void)ensureCapacityForAdditionalDataOfLength:(NSUInteger)bytesToRead
{
NSUInteger buffSize = [buffer length];
NSUInteger buffUsed = startOffset + bytesDone;
NSUInteger buffSpace = buffSize - buffUsed;
if (bytesToRead > buffSpace)
{
NSUInteger buffInc = bytesToRead - buffSpace;
[buffer increaseLengthBy:buffInc];
}
}
/**
* This method is used when we do NOT know how much data is available to be read from the socket.
* This method returns the default value unless it exceeds the specified readLength or maxLength.
*
* Furthermore, the shouldPreBuffer decision is based upon the packet type,
* and whether the returned value would fit in the current buffer without requiring a resize of the buffer.
**/
- (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr
{
NSUInteger result;
if (readLength > 0)
{
// Read a specific length of data
result = readLength - bytesDone;
// There is no need to prebuffer since we know exactly how much data we need to read.
// Even if the buffer isn't currently big enough to fit this amount of data,
// it would have to be resized eventually anyway.
if (shouldPreBufferPtr)
*shouldPreBufferPtr = NO;
}
else
{
// Either reading until we find a specified terminator,
// or we're simply reading all available data.
//
// In other words, one of:
//
// - readDataToData packet
// - readDataWithTimeout packet
if (maxLength > 0)
result = MIN(defaultValue, (maxLength - bytesDone));
else
result = defaultValue;
// Since we don't know the size of the read in advance,
// the shouldPreBuffer decision is based upon whether the returned value would fit
// in the current buffer without requiring a resize of the buffer.
//
// This is because, in all likelyhood, the amount read from the socket will be less than the default value.
// Thus we should avoid over-allocating the read buffer when we can simply use the pre-buffer instead.
if (shouldPreBufferPtr)
{
NSUInteger buffSize = [buffer length];
NSUInteger buffUsed = startOffset + bytesDone;
NSUInteger buffSpace = buffSize - buffUsed;
if (buffSpace >= result)
*shouldPreBufferPtr = NO;
else
*shouldPreBufferPtr = YES;
}
}
return result;
}
/**
* For read packets without a set terminator, returns the amount of data
* that can be read without exceeding the readLength or maxLength.
*
* The given parameter indicates the number of bytes estimated to be available on the socket,
* which is taken into consideration during the calculation.
*
* The given hint MUST be greater than zero.
**/
- (NSUInteger)readLengthForNonTermWithHint:(NSUInteger)bytesAvailable
{
NSAssert(term == nil, @"This method does not apply to term reads");
NSAssert(bytesAvailable > 0, @"Invalid parameter: bytesAvailable");
if (readLength > 0)
{
// Read a specific length of data
return MIN(bytesAvailable, (readLength - bytesDone));
// No need to avoid resizing the buffer.
// If the user provided their own buffer,
// and told us to read a certain length of data that exceeds the size of the buffer,
// then it is clear that our code will resize the buffer during the read operation.
//
// This method does not actually do any resizing.
// The resizing will happen elsewhere if needed.
}
else
{
// Read all available data
NSUInteger result = bytesAvailable;
if (maxLength > 0)
{
result = MIN(result, (maxLength - bytesDone));
}
// No need to avoid resizing the buffer.
// If the user provided their own buffer,
// and told us to read all available data without giving us a maxLength,
// then it is clear that our code might resize the buffer during the read operation.
//
// This method does not actually do any resizing.
// The resizing will happen elsewhere if needed.
return result;
}
}
/**
* For read packets with a set terminator, returns the amount of data
* that can be read without exceeding the maxLength.
*
* The given parameter indicates the number of bytes estimated to be available on the socket,
* which is taken into consideration during the calculation.
*
* To optimize memory allocations, mem copies, and mem moves
* the shouldPreBuffer boolean value will indicate if the data should be read into a prebuffer first,
* or if the data can be read directly into the read packet's buffer.
**/
- (NSUInteger)readLengthForTermWithHint:(NSUInteger)bytesAvailable shouldPreBuffer:(BOOL *)shouldPreBufferPtr
{
NSAssert(term != nil, @"This method does not apply to non-term reads");
NSAssert(bytesAvailable > 0, @"Invalid parameter: bytesAvailable");
NSUInteger result = bytesAvailable;
if (maxLength > 0)
{
result = MIN(result, (maxLength - bytesDone));
}
// Should the data be read into the read packet's buffer, or into a pre-buffer first?
//
// One would imagine the preferred option is the faster one.
// So which one is faster?
//
// Reading directly into the packet's buffer requires:
// 1. Possibly resizing packet buffer (malloc/realloc)
// 2. Filling buffer (read)
// 3. Searching for term (memcmp)
// 4. Possibly copying overflow into prebuffer (malloc/realloc, memcpy)
//
// Reading into prebuffer first:
// 1. Possibly resizing prebuffer (malloc/realloc)
// 2. Filling buffer (read)
// 3. Searching for term (memcmp)
// 4. Copying underflow into packet buffer (malloc/realloc, memcpy)
// 5. Removing underflow from prebuffer (memmove)
//
// Comparing the performance of the two we can see that reading
// data into the prebuffer first is slower due to the extra memove.
//
// However:
// The implementation of NSMutableData is open source via core foundation's CFMutableData.
// Decreasing the length of a mutable data object doesn't cause a realloc.
// In other words, the capacity of a mutable data object can grow, but doesn't shrink.
//
// This means the prebuffer will rarely need a realloc.
// The packet buffer, on the other hand, may often need a realloc.
// This is especially true if we are the buffer owner.
// Furthermore, if we are constantly realloc'ing the packet buffer,
// and then moving the overflow into the prebuffer,
// then we're consistently over-allocating memory for each term read.
// And now we get into a bit of a tradeoff between speed and memory utilization.
//
// The end result is that the two perform very similarly.
// And we can answer the original question very simply by another means.
//
// If we can read all the data directly into the packet's buffer without resizing it first,
// then we do so. Otherwise we use the prebuffer.
if (shouldPreBufferPtr)
{
NSUInteger buffSize = [buffer length];
NSUInteger buffUsed = startOffset + bytesDone;
if ((buffSize - buffUsed) >= result)
*shouldPreBufferPtr = NO;
else
*shouldPreBufferPtr = YES;
}
return result;
}
/**
* For read packets with a set terminator,
* returns the amount of data that can be read from the given preBuffer,
* without going over a terminator or the maxLength.
*
* It is assumed the terminator has not already been read.
**/
- (NSUInteger)readLengthForTermWithPreBuffer:(GCDAsyncSocketPreBuffer *)preBuffer found:(BOOL *)foundPtr
{
NSAssert(term != nil, @"This method does not apply to non-term reads");
NSAssert([preBuffer availableBytes] > 0, @"Invoked with empty pre buffer!");
// We know that the terminator, as a whole, doesn't exist in our own buffer.
// But it is possible that a _portion_ of it exists in our buffer.
// So we're going to look for the terminator starting with a portion of our own buffer.
//
// Example:
//
// term length = 3 bytes
// bytesDone = 5 bytes
// preBuffer length = 5 bytes
//
// If we append the preBuffer to our buffer,
// it would look like this:
//
// ---------------------
// |B|B|B|B|B|P|P|P|P|P|
// ---------------------
//
// So we start our search here:
//
// ---------------------
// |B|B|B|B|B|P|P|P|P|P|
// -------^-^-^---------
//
// And move forwards...
//
// ---------------------
// |B|B|B|B|B|P|P|P|P|P|
// ---------^-^-^-------
//
// Until we find the terminator or reach the end.
//
// ---------------------
// |B|B|B|B|B|P|P|P|P|P|
// ---------------^-^-^-
BOOL found = NO;
NSUInteger termLength = [term length];
NSUInteger preBufferLength = [preBuffer availableBytes];
if ((bytesDone + preBufferLength) < termLength)
{
// Not enough data for a full term sequence yet
return preBufferLength;
}
NSUInteger maxPreBufferLength;
if (maxLength > 0) {
maxPreBufferLength = MIN(preBufferLength, (maxLength - bytesDone));
// Note: maxLength >= termLength
}
else {
maxPreBufferLength = preBufferLength;
}
uint8_t seq[termLength];
const void *termBuf = [term bytes];
NSUInteger bufLen = MIN(bytesDone, (termLength - 1));
uint8_t *buf = (uint8_t *)[buffer mutableBytes] + startOffset + bytesDone - bufLen;
NSUInteger preLen = termLength - bufLen;
const uint8_t *pre = [preBuffer readBuffer];
NSUInteger loopCount = bufLen + maxPreBufferLength - termLength + 1; // Plus one. See example above.
NSUInteger result = maxPreBufferLength;
NSUInteger i;
for (i = 0; i < loopCount; i++)
{
if (bufLen > 0)
{
// Combining bytes from buffer and preBuffer
memcpy(seq, buf, bufLen);
memcpy(seq + bufLen, pre, preLen);
if (memcmp(seq, termBuf, termLength) == 0)
{
result = preLen;
found = YES;
break;
}
buf++;
bufLen--;
preLen++;
}
else
{
// Comparing directly from preBuffer
if (memcmp(pre, termBuf, termLength) == 0)
{
NSUInteger preOffset = pre - [preBuffer readBuffer]; // pointer arithmetic
result = preOffset + termLength;
found = YES;
break;
}
pre++;
}
}
// There is no need to avoid resizing the buffer in this particular situation.
if (foundPtr) *foundPtr = found;
return result;
}
/**
* For read packets with a set terminator, scans the packet buffer for the term.
* It is assumed the terminator had not been fully read prior to the new bytes.
*
* If the term is found, the number of excess bytes after the term are returned.
* If the term is not found, this method will return -1.
*
* Note: A return value of zero means the term was found at the very end.
*
* Prerequisites:
* The given number of bytes have been added to the end of our buffer.
* Our bytesDone variable has NOT been changed due to the prebuffered bytes.
**/
- (NSInteger)searchForTermAfterPreBuffering:(ssize_t)numBytes
{
NSAssert(term != nil, @"This method does not apply to non-term reads");
// The implementation of this method is very similar to the above method.
// See the above method for a discussion of the algorithm used here.
uint8_t *buff = [buffer mutableBytes];
NSUInteger buffLength = bytesDone + numBytes;
const void *termBuff = [term bytes];
NSUInteger termLength = [term length];
// Note: We are dealing with unsigned integers,
// so make sure the math doesn't go below zero.
NSUInteger i = ((buffLength - numBytes) >= termLength) ? (buffLength - numBytes - termLength + 1) : 0;
while (i + termLength <= buffLength)
{
uint8_t *subBuffer = buff + startOffset + i;
if (memcmp(subBuffer, termBuff, termLength) == 0)
{
return buffLength - (i + termLength);
}
i++;
}
return -1;
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* The GCDAsyncWritePacket encompasses the instructions for any given write.
**/
@interface GCDAsyncWritePacket : NSObject
{
@public
NSData *buffer;
NSUInteger bytesDone;
long tag;
NSTimeInterval timeout;
}
- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i NS_DESIGNATED_INITIALIZER;
@end
@implementation GCDAsyncWritePacket
// Cover the superclass' designated initializer
- (instancetype)init NS_UNAVAILABLE
{
NSAssert(0, @"Use the designated initializer");
return nil;
}
- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i
{
if((self = [super init]))
{
buffer = d; // Retain not copy. For performance as documented in header file.
bytesDone = 0;
timeout = t;
tag = i;
}
return self;
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* The GCDAsyncSpecialPacket encompasses special instructions for interruptions in the read/write queues.
* This class my be altered to support more than just TLS in the future.
**/
@interface GCDAsyncSpecialPacket : NSObject
{
@public
NSDictionary *tlsSettings;
}
- (instancetype)initWithTLSSettings:(NSDictionary *)settings NS_DESIGNATED_INITIALIZER;
@end
@implementation GCDAsyncSpecialPacket
// Cover the superclass' designated initializer
- (instancetype)init NS_UNAVAILABLE
{
NSAssert(0, @"Use the designated initializer");
return nil;
}
- (instancetype)initWithTLSSettings:(NSDictionary *)settings
{
if((self = [super init]))
{
tlsSettings = [settings copy];
}
return self;
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation GCDAsyncSocket
{
uint32_t flags;
uint16_t config;
__weak id delegate;
dispatch_queue_t delegateQueue;
int socket4FD;
int socket6FD;
int socketUN;
NSURL *socketUrl;
int stateIndex;
NSData * connectInterface4;
NSData * connectInterface6;
NSData * connectInterfaceUN;
dispatch_queue_t socketQueue;
dispatch_source_t accept4Source;
dispatch_source_t accept6Source;
dispatch_source_t acceptUNSource;
dispatch_source_t connectTimer;
dispatch_source_t readSource;
dispatch_source_t writeSource;
dispatch_source_t readTimer;
dispatch_source_t writeTimer;
NSMutableArray *readQueue;
NSMutableArray *writeQueue;
GCDAsyncReadPacket *currentRead;
GCDAsyncWritePacket *currentWrite;
unsigned long socketFDBytesAvailable;
GCDAsyncSocketPreBuffer *preBuffer;
#if TARGET_OS_IPHONE
CFStreamClientContext streamContext;
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
#endif
SSLContextRef sslContext;
GCDAsyncSocketPreBuffer *sslPreBuffer;
size_t sslWriteCachedLength;
OSStatus sslErrCode;
OSStatus lastSSLHandshakeError;
void *IsOnSocketQueueOrTargetQueueKey;
id userData;
NSTimeInterval alternateAddressDelay;
}
- (instancetype)init
{
return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL];
}
- (instancetype)initWithSocketQueue:(dispatch_queue_t)sq
{
return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq];
}
- (instancetype)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq
{
return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL];
}
- (instancetype)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq
{
if((self = [super init]))
{
delegate = aDelegate;
delegateQueue = dq;
#if !OS_OBJECT_USE_OBJC
if (dq) dispatch_retain(dq);
#endif
socket4FD = SOCKET_NULL;
socket6FD = SOCKET_NULL;
socketUN = SOCKET_NULL;
socketUrl = nil;
stateIndex = 0;
if (sq)
{
NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
@"The given socketQueue parameter must not be a concurrent queue.");
NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
@"The given socketQueue parameter must not be a concurrent queue.");
NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
@"The given socketQueue parameter must not be a concurrent queue.");
socketQueue = sq;
#if !OS_OBJECT_USE_OBJC
dispatch_retain(sq);
#endif
}
else
{
socketQueue = dispatch_queue_create([GCDAsyncSocketQueueName UTF8String], NULL);
}
// The dispatch_queue_set_specific() and dispatch_get_specific() functions take a "void *key" parameter.
// From the documentation:
//
// > Keys are only compared as pointers and are never dereferenced.
// > Thus, you can use a pointer to a static variable for a specific subsystem or
// > any other value that allows you to identify the value uniquely.
//
// We're just going to use the memory address of an ivar.
// Specifically an ivar that is explicitly named for our purpose to make the code more readable.
//
// However, it feels tedious (and less readable) to include the "&" all the time:
// dispatch_get_specific(&IsOnSocketQueueOrTargetQueueKey)
//
// So we're going to make it so it doesn't matter if we use the '&' or not,
// by assigning the value of the ivar to the address of the ivar.
// Thus: IsOnSocketQueueOrTargetQueueKey == &IsOnSocketQueueOrTargetQueueKey;
IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey;
void *nonNullUnusedPointer = (__bridge void *)self;
dispatch_queue_set_specific(socketQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL);
readQueue = [[NSMutableArray alloc] initWithCapacity:5];
currentRead = nil;
writeQueue = [[NSMutableArray alloc] initWithCapacity:5];
currentWrite = nil;
preBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)];
alternateAddressDelay = 0.3;
}
return self;
}
- (void)dealloc
{
LogInfo(@"%@ - %@ (start)", THIS_METHOD, self);
// Set dealloc flag.
// This is used by closeWithError to ensure we don't accidentally retain ourself.
flags |= kDealloc;
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
[self closeWithError:nil];
}
else
{
dispatch_sync(socketQueue, ^{
[self closeWithError:nil];
});
}
delegate = nil;
#if !OS_OBJECT_USE_OBJC
if (delegateQueue) dispatch_release(delegateQueue);
#endif
delegateQueue = NULL;
#if !OS_OBJECT_USE_OBJC
if (socketQueue) dispatch_release(socketQueue);
#endif
socketQueue = NULL;
LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self);
}
#pragma mark -
+ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD socketQueue:(nullable dispatch_queue_t)sq error:(NSError**)error {
return [self socketFromConnectedSocketFD:socketFD delegate:nil delegateQueue:NULL socketQueue:sq error:error];
}
+ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq error:(NSError**)error {
return [self socketFromConnectedSocketFD:socketFD delegate:aDelegate delegateQueue:dq socketQueue:NULL error:error];
}
+ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq error:(NSError* __autoreleasing *)error
{
__block BOOL errorOccured = NO;
GCDAsyncSocket *socket = [[[self class] alloc] initWithDelegate:aDelegate delegateQueue:dq socketQueue:sq];
dispatch_sync(socket->socketQueue, ^{ @autoreleasepool {
struct sockaddr addr;
socklen_t addr_size = sizeof(struct sockaddr);
int retVal = getpeername(socketFD, (struct sockaddr *)&addr, &addr_size);
if (retVal)
{
NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketOtherError",
@"GCDAsyncSocket", [NSBundle mainBundle],
@"Attempt to create socket from socket FD failed. getpeername() failed", nil);
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
errorOccured = YES;
if (error)
*error = [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketOtherError userInfo:userInfo];
return;
}
if (addr.sa_family == AF_INET)
{
socket->socket4FD = socketFD;
}
else if (addr.sa_family == AF_INET6)
{
socket->socket6FD = socketFD;
}
else
{
NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketOtherError",
@"GCDAsyncSocket", [NSBundle mainBundle],
@"Attempt to create socket from socket FD failed. socket FD is neither IPv4 nor IPv6", nil);
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
errorOccured = YES;
if (error)
*error = [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketOtherError userInfo:userInfo];
return;
}
socket->flags = kSocketStarted;
[socket didConnect:socket->stateIndex];
}});
return errorOccured? nil: socket;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Configuration
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (id)delegate
{
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
return delegate;
}
else
{
__block id result;
dispatch_sync(socketQueue, ^{
result = self->delegate;
});
return result;
}
}
- (void)setDelegate:(id)newDelegate synchronously:(BOOL)synchronously
{
dispatch_block_t block = ^{
self->delegate = newDelegate;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
block();
}
else {
if (synchronously)
dispatch_sync(socketQueue, block);
else
dispatch_async(socketQueue, block);
}
}
- (void)setDelegate:(id)newDelegate
{
[self setDelegate:newDelegate synchronously:NO];
}
- (void)synchronouslySetDelegate:(id)newDelegate
{
[self setDelegate:newDelegate synchronously:YES];
}
- (dispatch_queue_t)delegateQueue
{
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
return delegateQueue;
}
else
{
__block dispatch_queue_t result;
dispatch_sync(socketQueue, ^{
result = self->delegateQueue;
});
return result;
}
}
- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously
{
dispatch_block_t block = ^{
#if !OS_OBJECT_USE_OBJC
if (self->delegateQueue) dispatch_release(self->delegateQueue);
if (newDelegateQueue) dispatch_retain(newDelegateQueue);
#endif
self->delegateQueue = newDelegateQueue;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
block();
}
else {
if (synchronously)
dispatch_sync(socketQueue, block);
else
dispatch_async(socketQueue, block);
}
}
- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue
{
[self setDelegateQueue:newDelegateQueue synchronously:NO];
}
- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)newDelegateQueue
{
[self setDelegateQueue:newDelegateQueue synchronously:YES];
}
- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr
{
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
if (delegatePtr) *delegatePtr = delegate;
if (delegateQueuePtr) *delegateQueuePtr = delegateQueue;
}
else
{
__block id dPtr = NULL;
__block dispatch_queue_t dqPtr = NULL;
dispatch_sync(socketQueue, ^{
dPtr = self->delegate;
dqPtr = self->delegateQueue;
});
if (delegatePtr) *delegatePtr = dPtr;
if (delegateQueuePtr) *delegateQueuePtr = dqPtr;
}
}
- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously
{
dispatch_block_t block = ^{
self->delegate = newDelegate;
#if !OS_OBJECT_USE_OBJC
if (self->delegateQueue) dispatch_release(self->delegateQueue);
if (newDelegateQueue) dispatch_retain(newDelegateQueue);
#endif
self->delegateQueue = newDelegateQueue;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
block();
}
else {
if (synchronously)
dispatch_sync(socketQueue, block);
else
dispatch_async(socketQueue, block);
}
}
- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue
{
[self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:NO];
}
- (void)synchronouslySetDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue
{
[self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:YES];
}
- (BOOL)isIPv4Enabled
{
// Note: YES means kIPv4Disabled is OFF
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
return ((config & kIPv4Disabled) == 0);
}
else
{
__block BOOL result;
dispatch_sync(socketQueue, ^{
result = ((self->config & kIPv4Disabled) == 0);
});
return result;
}
}
- (void)setIPv4Enabled:(BOOL)flag
{
// Note: YES means kIPv4Disabled is OFF
dispatch_block_t block = ^{
if (flag)
self->config &= ~kIPv4Disabled;
else
self->config |= kIPv4Disabled;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
- (BOOL)isIPv6Enabled
{
// Note: YES means kIPv6Disabled is OFF
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
return ((config & kIPv6Disabled) == 0);
}
else
{
__block BOOL result;
dispatch_sync(socketQueue, ^{
result = ((self->config & kIPv6Disabled) == 0);
});
return result;
}
}
- (void)setIPv6Enabled:(BOOL)flag
{
// Note: YES means kIPv6Disabled is OFF
dispatch_block_t block = ^{
if (flag)
self->config &= ~kIPv6Disabled;
else
self->config |= kIPv6Disabled;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
- (BOOL)isIPv4PreferredOverIPv6
{
// Note: YES means kPreferIPv6 is OFF
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
return ((config & kPreferIPv6) == 0);
}
else
{
__block BOOL result;
dispatch_sync(socketQueue, ^{
result = ((self->config & kPreferIPv6) == 0);
});
return result;
}
}
- (void)setIPv4PreferredOverIPv6:(BOOL)flag
{
// Note: YES means kPreferIPv6 is OFF
dispatch_block_t block = ^{
if (flag)
self->config &= ~kPreferIPv6;
else
self->config |= kPreferIPv6;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
- (NSTimeInterval) alternateAddressDelay {
__block NSTimeInterval delay;
dispatch_block_t block = ^{
delay = self->alternateAddressDelay;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return delay;
}
- (void) setAlternateAddressDelay:(NSTimeInterval)delay {
dispatch_block_t block = ^{
self->alternateAddressDelay = delay;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
- (id)userData
{
__block id result = nil;
dispatch_block_t block = ^{
result = self->userData;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (void)setUserData:(id)arbitraryUserData
{
dispatch_block_t block = ^{
if (self->userData != arbitraryUserData)
{
self->userData = arbitraryUserData;
}
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Accepting
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)acceptOnPort:(uint16_t)port error:(NSError **)errPtr
{
return [self acceptOnInterface:nil port:port error:errPtr];
}
- (BOOL)acceptOnInterface:(NSString *)inInterface port:(uint16_t)port error:(NSError **)errPtr
{
LogTrace();
// Just in-case interface parameter is immutable.
NSString *interface = [inInterface copy];
__block BOOL result = NO;
__block NSError *err = nil;
// CreateSocket Block
// This block will be invoked within the dispatch block below.
int(^createSocket)(int, NSData*) = ^int (int domain, NSData *interfaceAddr) {
int socketFD = socket(domain, SOCK_STREAM, 0);
if (socketFD == SOCKET_NULL)
{
NSString *reason = @"Error in socket() function";
err = [self errorWithErrno:errno reason:reason];
return SOCKET_NULL;
}
int status;
// Set socket options
status = fcntl(socketFD, F_SETFL, O_NONBLOCK);
if (status == -1)
{
NSString *reason = @"Error enabling non-blocking IO on socket (fcntl)";
err = [self errorWithErrno:errno reason:reason];
LogVerbose(@"close(socketFD)");
close(socketFD);
return SOCKET_NULL;
}
int reuseOn = 1;
status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
if (status == -1)
{
NSString *reason = @"Error enabling address reuse (setsockopt)";
err = [self errorWithErrno:errno reason:reason];
LogVerbose(@"close(socketFD)");
close(socketFD);
return SOCKET_NULL;
}
// Bind socket
status = bind(socketFD, (const struct sockaddr *)[interfaceAddr bytes], (socklen_t)[interfaceAddr length]);
if (status == -1)
{
NSString *reason = @"Error in bind() function";
err = [self errorWithErrno:errno reason:reason];
LogVerbose(@"close(socketFD)");
close(socketFD);
return SOCKET_NULL;
}
// Listen
status = listen(socketFD, 1024);
if (status == -1)
{
NSString *reason = @"Error in listen() function";
err = [self errorWithErrno:errno reason:reason];
LogVerbose(@"close(socketFD)");
close(socketFD);
return SOCKET_NULL;
}
return socketFD;
};
// Create dispatch block and run on socketQueue
dispatch_block_t block = ^{ @autoreleasepool {
if (self->delegate == nil) // Must have delegate set
{
NSString *msg = @"Attempting to accept without a delegate. Set a delegate first.";
err = [self badConfigError:msg];
return_from_block;
}
if (self->delegateQueue == NULL) // Must have delegate queue set
{
NSString *msg = @"Attempting to accept without a delegate queue. Set a delegate queue first.";
err = [self badConfigError:msg];
return_from_block;
}
BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO;
BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO;
if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled
{
NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first.";
err = [self badConfigError:msg];
return_from_block;
}
if (![self isDisconnected]) // Must be disconnected
{
NSString *msg = @"Attempting to accept while connected or accepting connections. Disconnect first.";
err = [self badConfigError:msg];
return_from_block;
}
// Clear queues (spurious read/write requests post disconnect)
[self->readQueue removeAllObjects];
[self->writeQueue removeAllObjects];
// Resolve interface from description
NSMutableData *interface4 = nil;
NSMutableData *interface6 = nil;
[self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:port];
if ((interface4 == nil) && (interface6 == nil))
{
NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
err = [self badParamError:msg];
return_from_block;
}
if (isIPv4Disabled && (interface6 == nil))
{
NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6.";
err = [self badParamError:msg];
return_from_block;
}
if (isIPv6Disabled && (interface4 == nil))
{
NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4.";
err = [self badParamError:msg];
return_from_block;
}
BOOL enableIPv4 = !isIPv4Disabled && (interface4 != nil);
BOOL enableIPv6 = !isIPv6Disabled && (interface6 != nil);
// Create sockets, configure, bind, and listen
if (enableIPv4)
{
LogVerbose(@"Creating IPv4 socket");
self->socket4FD = createSocket(AF_INET, interface4);
if (self->socket4FD == SOCKET_NULL)
{
return_from_block;
}
}
if (enableIPv6)
{
LogVerbose(@"Creating IPv6 socket");
if (enableIPv4 && (port == 0))
{
// No specific port was specified, so we allowed the OS to pick an available port for us.
// Now we need to make sure the IPv6 socket listens on the same port as the IPv4 socket.
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)[interface6 mutableBytes];
addr6->sin6_port = htons([self localPort4]);
}
self->socket6FD = createSocket(AF_INET6, interface6);
if (self->socket6FD == SOCKET_NULL)
{
if (self->socket4FD != SOCKET_NULL)
{
LogVerbose(@"close(socket4FD)");
close(self->socket4FD);
self->socket4FD = SOCKET_NULL;
}
return_from_block;
}
}
// Create accept sources
if (enableIPv4)
{
self->accept4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, self->socket4FD, 0, self->socketQueue);
int socketFD = self->socket4FD;
dispatch_source_t acceptSource = self->accept4Source;
__weak GCDAsyncSocket *weakSelf = self;
dispatch_source_set_event_handler(self->accept4Source, ^{ @autoreleasepool {
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
__strong GCDAsyncSocket *strongSelf = weakSelf;
if (strongSelf == nil) return_from_block;
LogVerbose(@"event4Block");
unsigned long i = 0;
unsigned long numPendingConnections = dispatch_source_get_data(acceptSource);
LogVerbose(@"numPendingConnections: %lu", numPendingConnections);
while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections));
#pragma clang diagnostic pop
}});
dispatch_source_set_cancel_handler(self->accept4Source, ^{
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
#if !OS_OBJECT_USE_OBJC
LogVerbose(@"dispatch_release(accept4Source)");
dispatch_release(acceptSource);
#endif
LogVerbose(@"close(socket4FD)");
close(socketFD);
#pragma clang diagnostic pop
});
LogVerbose(@"dispatch_resume(accept4Source)");
dispatch_resume(self->accept4Source);
}
if (enableIPv6)
{
self->accept6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, self->socket6FD, 0, self->socketQueue);
int socketFD = self->socket6FD;
dispatch_source_t acceptSource = self->accept6Source;
__weak GCDAsyncSocket *weakSelf = self;
dispatch_source_set_event_handler(self->accept6Source, ^{ @autoreleasepool {
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
__strong GCDAsyncSocket *strongSelf = weakSelf;
if (strongSelf == nil) return_from_block;
LogVerbose(@"event6Block");
unsigned long i = 0;
unsigned long numPendingConnections = dispatch_source_get_data(acceptSource);
LogVerbose(@"numPendingConnections: %lu", numPendingConnections);
while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections));
#pragma clang diagnostic pop
}});
dispatch_source_set_cancel_handler(self->accept6Source, ^{
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
#if !OS_OBJECT_USE_OBJC
LogVerbose(@"dispatch_release(accept6Source)");
dispatch_release(acceptSource);
#endif
LogVerbose(@"close(socket6FD)");
close(socketFD);
#pragma clang diagnostic pop
});
LogVerbose(@"dispatch_resume(accept6Source)");
dispatch_resume(self->accept6Source);
}
self->flags |= kSocketStarted;
result = YES;
}};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
if (result == NO)
{
LogInfo(@"Error in accept: %@", err);
if (errPtr)
*errPtr = err;
}
return result;
}
- (BOOL)acceptOnUrl:(NSURL *)url error:(NSError **)errPtr
{
LogTrace();
__block BOOL result = NO;
__block NSError *err = nil;
// CreateSocket Block
// This block will be invoked within the dispatch block below.
int(^createSocket)(int, NSData*) = ^int (int domain, NSData *interfaceAddr) {
int socketFD = socket(domain, SOCK_STREAM, 0);
if (socketFD == SOCKET_NULL)
{
NSString *reason = @"Error in socket() function";
err = [self errorWithErrno:errno reason:reason];
return SOCKET_NULL;
}
int status;
// Set socket options
status = fcntl(socketFD, F_SETFL, O_NONBLOCK);
if (status == -1)
{
NSString *reason = @"Error enabling non-blocking IO on socket (fcntl)";
err = [self errorWithErrno:errno reason:reason];
LogVerbose(@"close(socketFD)");
close(socketFD);
return SOCKET_NULL;
}
int reuseOn = 1;
status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
if (status == -1)
{
NSString *reason = @"Error enabling address reuse (setsockopt)";
err = [self errorWithErrno:errno reason:reason];
LogVerbose(@"close(socketFD)");
close(socketFD);
return SOCKET_NULL;
}
// Bind socket
status = bind(socketFD, (const struct sockaddr *)[interfaceAddr bytes], (socklen_t)[interfaceAddr length]);
if (status == -1)
{
NSString *reason = @"Error in bind() function";
err = [self errorWithErrno:errno reason:reason];
LogVerbose(@"close(socketFD)");
close(socketFD);
return SOCKET_NULL;
}
// Listen
status = listen(socketFD, 1024);
if (status == -1)
{
NSString *reason = @"Error in listen() function";
err = [self errorWithErrno:errno reason:reason];
LogVerbose(@"close(socketFD)");
close(socketFD);
return SOCKET_NULL;
}
return socketFD;
};
// Create dispatch block and run on socketQueue
dispatch_block_t block = ^{ @autoreleasepool {
if (self->delegate == nil) // Must have delegate set
{
NSString *msg = @"Attempting to accept without a delegate. Set a delegate first.";
err = [self badConfigError:msg];
return_from_block;
}
if (self->delegateQueue == NULL) // Must have delegate queue set
{
NSString *msg = @"Attempting to accept without a delegate queue. Set a delegate queue first.";
err = [self badConfigError:msg];
return_from_block;
}
if (![self isDisconnected]) // Must be disconnected
{
NSString *msg = @"Attempting to accept while connected or accepting connections. Disconnect first.";
err = [self badConfigError:msg];
return_from_block;
}
// Clear queues (spurious read/write requests post disconnect)
[self->readQueue removeAllObjects];
[self->writeQueue removeAllObjects];
// Remove a previous socket
NSError *error = nil;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *urlPath = url.path;
if (urlPath && [fileManager fileExistsAtPath:urlPath]) {
if (![fileManager removeItemAtURL:url error:&error]) {
NSString *msg = @"Could not remove previous unix domain socket at given url.";
err = [self otherError:msg];
return_from_block;
}
}
// Resolve interface from description
NSData *interface = [self getInterfaceAddressFromUrl:url];
if (interface == nil)
{
NSString *msg = @"Invalid unix domain url. Specify a valid file url that does not exist (e.g. \"file:///tmp/socket\")";
err = [self badParamError:msg];
return_from_block;
}
// Create sockets, configure, bind, and listen
LogVerbose(@"Creating unix domain socket");
self->socketUN = createSocket(AF_UNIX, interface);
if (self->socketUN == SOCKET_NULL)
{
return_from_block;
}
self->socketUrl = url;
// Create accept sources
self->acceptUNSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, self->socketUN, 0, self->socketQueue);
int socketFD = self->socketUN;
dispatch_source_t acceptSource = self->acceptUNSource;
__weak GCDAsyncSocket *weakSelf = self;
dispatch_source_set_event_handler(self->acceptUNSource, ^{ @autoreleasepool {
__strong GCDAsyncSocket *strongSelf = weakSelf;
LogVerbose(@"eventUNBlock");
unsigned long i = 0;
unsigned long numPendingConnections = dispatch_source_get_data(acceptSource);
LogVerbose(@"numPendingConnections: %lu", numPendingConnections);
while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections));
}});
dispatch_source_set_cancel_handler(self->acceptUNSource, ^{
#if !OS_OBJECT_USE_OBJC
LogVerbose(@"dispatch_release(acceptUNSource)");
dispatch_release(acceptSource);
#endif
LogVerbose(@"close(socketUN)");
close(socketFD);
});
LogVerbose(@"dispatch_resume(acceptUNSource)");
dispatch_resume(self->acceptUNSource);
self->flags |= kSocketStarted;
result = YES;
}};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
if (result == NO)
{
LogInfo(@"Error in accept: %@", err);
if (errPtr)
*errPtr = err;
}
return result;
}
- (BOOL)doAccept:(int)parentSocketFD
{
LogTrace();
int socketType;
int childSocketFD;
NSData *childSocketAddress;
if (parentSocketFD == socket4FD)
{
socketType = 0;
struct sockaddr_in addr;
socklen_t addrLen = sizeof(addr);
childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen);
if (childSocketFD == -1)
{
LogWarn(@"Accept failed with error: %@", [self errnoError]);
return NO;
}
childSocketAddress = [NSData dataWithBytes:&addr length:addrLen];
}
else if (parentSocketFD == socket6FD)
{
socketType = 1;
struct sockaddr_in6 addr;
socklen_t addrLen = sizeof(addr);
childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen);
if (childSocketFD == -1)
{
LogWarn(@"Accept failed with error: %@", [self errnoError]);
return NO;
}
childSocketAddress = [NSData dataWithBytes:&addr length:addrLen];
}
else // if (parentSocketFD == socketUN)
{
socketType = 2;
struct sockaddr_un addr;
socklen_t addrLen = sizeof(addr);
childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen);
if (childSocketFD == -1)
{
LogWarn(@"Accept failed with error: %@", [self errnoError]);
return NO;
}
childSocketAddress = [NSData dataWithBytes:&addr length:addrLen];
}
// Enable non-blocking IO on the socket
int result = fcntl(childSocketFD, F_SETFL, O_NONBLOCK);
if (result == -1)
{
LogWarn(@"Error enabling non-blocking IO on accepted socket (fcntl)");
LogVerbose(@"close(childSocketFD)");
close(childSocketFD);
return NO;
}
// Prevent SIGPIPE signals
int nosigpipe = 1;
setsockopt(childSocketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
// Notify delegate
if (delegateQueue)
{
__strong id theDelegate = delegate;
dispatch_async(delegateQueue, ^{ @autoreleasepool {
// Query delegate for custom socket queue
dispatch_queue_t childSocketQueue = NULL;
if ([theDelegate respondsToSelector:@selector(newSocketQueueForConnectionFromAddress:onSocket:)])
{
childSocketQueue = [theDelegate newSocketQueueForConnectionFromAddress:childSocketAddress
onSocket:self];
}
// Create GCDAsyncSocket instance for accepted socket
GCDAsyncSocket *acceptedSocket = [[[self class] alloc] initWithDelegate:theDelegate
delegateQueue:self->delegateQueue
socketQueue:childSocketQueue];
if (socketType == 0)
acceptedSocket->socket4FD = childSocketFD;
else if (socketType == 1)
acceptedSocket->socket6FD = childSocketFD;
else
acceptedSocket->socketUN = childSocketFD;
acceptedSocket->flags = (kSocketStarted | kConnected);
// Setup read and write sources for accepted socket
dispatch_async(acceptedSocket->socketQueue, ^{ @autoreleasepool {
[acceptedSocket setupReadAndWriteSourcesForNewlyConnectedSocket:childSocketFD];
}});
// Notify delegate
if ([theDelegate respondsToSelector:@selector(socket:didAcceptNewSocket:)])
{
[theDelegate socket:self didAcceptNewSocket:acceptedSocket];
}
// Release the socket queue returned from the delegate (it was retained by acceptedSocket)
#if !OS_OBJECT_USE_OBJC
if (childSocketQueue) dispatch_release(childSocketQueue);
#endif
// The accepted socket should have been retained by the delegate.
// Otherwise it gets properly released when exiting the block.
}});
}
return YES;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Connecting
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* This method runs through the various checks required prior to a connection attempt.
* It is shared between the connectToHost and connectToAddress methods.
*
**/
- (BOOL)preConnectWithInterface:(NSString *)interface error:(NSError **)errPtr
{
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
if (delegate == nil) // Must have delegate set
{
if (errPtr)
{
NSString *msg = @"Attempting to connect without a delegate. Set a delegate first.";
*errPtr = [self badConfigError:msg];
}
return NO;
}
if (delegateQueue == NULL) // Must have delegate queue set
{
if (errPtr)
{
NSString *msg = @"Attempting to connect without a delegate queue. Set a delegate queue first.";
*errPtr = [self badConfigError:msg];
}
return NO;
}
if (![self isDisconnected]) // Must be disconnected
{
if (errPtr)
{
NSString *msg = @"Attempting to connect while connected or accepting connections. Disconnect first.";
*errPtr = [self badConfigError:msg];
}
return NO;
}
BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled
{
if (errPtr)
{
NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first.";
*errPtr = [self badConfigError:msg];
}
return NO;
}
if (interface)
{
NSMutableData *interface4 = nil;
NSMutableData *interface6 = nil;
[self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:0];
if ((interface4 == nil) && (interface6 == nil))
{
if (errPtr)
{
NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
*errPtr = [self badParamError:msg];
}
return NO;
}
if (isIPv4Disabled && (interface6 == nil))
{
if (errPtr)
{
NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6.";
*errPtr = [self badParamError:msg];
}
return NO;
}
if (isIPv6Disabled && (interface4 == nil))
{
if (errPtr)
{
NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4.";
*errPtr = [self badParamError:msg];
}
return NO;
}
connectInterface4 = interface4;
connectInterface6 = interface6;
}
// Clear queues (spurious read/write requests post disconnect)
[readQueue removeAllObjects];
[writeQueue removeAllObjects];
return YES;
}
- (BOOL)preConnectWithUrl:(NSURL *)url error:(NSError **)errPtr
{
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
if (delegate == nil) // Must have delegate set
{
if (errPtr)
{
NSString *msg = @"Attempting to connect without a delegate. Set a delegate first.";
*errPtr = [self badConfigError:msg];
}
return NO;
}
if (delegateQueue == NULL) // Must have delegate queue set
{
if (errPtr)
{
NSString *msg = @"Attempting to connect without a delegate queue. Set a delegate queue first.";
*errPtr = [self badConfigError:msg];
}
return NO;
}
if (![self isDisconnected]) // Must be disconnected
{
if (errPtr)
{
NSString *msg = @"Attempting to connect while connected or accepting connections. Disconnect first.";
*errPtr = [self badConfigError:msg];
}
return NO;
}
NSData *interface = [self getInterfaceAddressFromUrl:url];
if (interface == nil)
{
if (errPtr)
{
NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
*errPtr = [self badParamError:msg];
}
return NO;
}
connectInterfaceUN = interface;
// Clear queues (spurious read/write requests post disconnect)
[readQueue removeAllObjects];
[writeQueue removeAllObjects];
return YES;
}
- (BOOL)connectToHost:(NSString*)host onPort:(uint16_t)port error:(NSError **)errPtr
{
return [self connectToHost:host onPort:port withTimeout:-1 error:errPtr];
}
- (BOOL)connectToHost:(NSString *)host
onPort:(uint16_t)port
withTimeout:(NSTimeInterval)timeout
error:(NSError **)errPtr
{
return [self connectToHost:host onPort:port viaInterface:nil withTimeout:timeout error:errPtr];
}
- (BOOL)connectToHost:(NSString *)inHost
onPort:(uint16_t)port
viaInterface:(NSString *)inInterface
withTimeout:(NSTimeInterval)timeout
error:(NSError **)errPtr
{
LogTrace();
// Just in case immutable objects were passed
NSString *host = [inHost copy];
NSString *interface = [inInterface copy];
__block BOOL result = NO;
__block NSError *preConnectErr = nil;
dispatch_block_t block = ^{ @autoreleasepool {
// Check for problems with host parameter
if ([host length] == 0)
{
NSString *msg = @"Invalid host parameter (nil or \"\"). Should be a domain name or IP address string.";
preConnectErr = [self badParamError:msg];
return_from_block;
}
// Run through standard pre-connect checks
if (![self preConnectWithInterface:interface error:&preConnectErr])
{
return_from_block;
}
// We've made it past all the checks.
// It's time to start the connection process.
self->flags |= kSocketStarted;
LogVerbose(@"Dispatching DNS lookup...");
// It's possible that the given host parameter is actually a NSMutableString.
// So we want to copy it now, within this block that will be executed synchronously.
// This way the asynchronous lookup block below doesn't have to worry about it changing.
NSString *hostCpy = [host copy];
int aStateIndex = self->stateIndex;
__weak GCDAsyncSocket *weakSelf = self;
dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool {
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
NSError *lookupErr = nil;
NSMutableArray *addresses = [[self class] lookupHost:hostCpy port:port error:&lookupErr];
__strong GCDAsyncSocket *strongSelf = weakSelf;
if (strongSelf == nil) return_from_block;
if (lookupErr)
{
dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
[strongSelf lookup:aStateIndex didFail:lookupErr];
}});
}
else
{
NSData *address4 = nil;
NSData *address6 = nil;
for (NSData *address in addresses)
{
if (!address4 && [[self class] isIPv4Address:address])
{
address4 = address;
}
else if (!address6 && [[self class] isIPv6Address:address])
{
address6 = address;
}
}
dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
[strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6];
}});
}
#pragma clang diagnostic pop
}});
[self startConnectTimeout:timeout];
result = YES;
}};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
if (errPtr) *errPtr = preConnectErr;
return result;
}
- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr
{
return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:-1 error:errPtr];
}
- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr
{
return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:timeout error:errPtr];
}
- (BOOL)connectToAddress:(NSData *)inRemoteAddr
viaInterface:(NSString *)inInterface
withTimeout:(NSTimeInterval)timeout
error:(NSError **)errPtr
{
LogTrace();
// Just in case immutable objects were passed
NSData *remoteAddr = [inRemoteAddr copy];
NSString *interface = [inInterface copy];
__block BOOL result = NO;
__block NSError *err = nil;
dispatch_block_t block = ^{ @autoreleasepool {
// Check for problems with remoteAddr parameter
NSData *address4 = nil;
NSData *address6 = nil;
if ([remoteAddr length] >= sizeof(struct sockaddr))
{
const struct sockaddr *sockaddr = (const struct sockaddr *)[remoteAddr bytes];
if (sockaddr->sa_family == AF_INET)
{
if ([remoteAddr length] == sizeof(struct sockaddr_in))
{
address4 = remoteAddr;
}
}
else if (sockaddr->sa_family == AF_INET6)
{
if ([remoteAddr length] == sizeof(struct sockaddr_in6))
{
address6 = remoteAddr;
}
}
}
if ((address4 == nil) && (address6 == nil))
{
NSString *msg = @"A valid IPv4 or IPv6 address was not given";
err = [self badParamError:msg];
return_from_block;
}
BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO;
BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO;
if (isIPv4Disabled && (address4 != nil))
{
NSString *msg = @"IPv4 has been disabled and an IPv4 address was passed.";
err = [self badParamError:msg];
return_from_block;
}
if (isIPv6Disabled && (address6 != nil))
{
NSString *msg = @"IPv6 has been disabled and an IPv6 address was passed.";
err = [self badParamError:msg];
return_from_block;
}
// Run through standard pre-connect checks
if (![self preConnectWithInterface:interface error:&err])
{
return_from_block;
}
// We've made it past all the checks.
// It's time to start the connection process.
if (![self connectWithAddress4:address4 address6:address6 error:&err])
{
return_from_block;
}
self->flags |= kSocketStarted;
[self startConnectTimeout:timeout];
result = YES;
}};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
if (result == NO)
{
if (errPtr)
*errPtr = err;
}
return result;
}
- (BOOL)connectToUrl:(NSURL *)url withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr
{
LogTrace();
__block BOOL result = NO;
__block NSError *err = nil;
dispatch_block_t block = ^{ @autoreleasepool {
// Check for problems with host parameter
if ([url.path length] == 0)
{
NSString *msg = @"Invalid unix domain socket url.";
err = [self badParamError:msg];
return_from_block;
}
// Run through standard pre-connect checks
if (![self preConnectWithUrl:url error:&err])
{
return_from_block;
}
// We've made it past all the checks.
// It's time to start the connection process.
self->flags |= kSocketStarted;
// Start the normal connection process
NSError *connectError = nil;
if (![self connectWithAddressUN:self->connectInterfaceUN error:&connectError])
{
[self closeWithError:connectError];
return_from_block;
}
[self startConnectTimeout:timeout];
result = YES;
}};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
if (result == NO)
{
if (errPtr)
*errPtr = err;
}
return result;
}
- (BOOL)connectToNetService:(NSNetService *)netService error:(NSError **)errPtr
{
NSArray* addresses = [netService addresses];
for (NSData* address in addresses)
{
BOOL result = [self connectToAddress:address error:errPtr];
if (result)
{
return YES;
}
}
return NO;
}
- (void)lookup:(int)aStateIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
NSAssert(address4 || address6, @"Expected at least one valid address");
if (aStateIndex != stateIndex)
{
LogInfo(@"Ignoring lookupDidSucceed, already disconnected");
// The connect operation has been cancelled.
// That is, socket was disconnected, or connection has already timed out.
return;
}
// Check for problems
BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
if (isIPv4Disabled && (address6 == nil))
{
NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address.";
[self closeWithError:[self otherError:msg]];
return;
}
if (isIPv6Disabled && (address4 == nil))
{
NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address.";
[self closeWithError:[self otherError:msg]];
return;
}
// Start the normal connection process
NSError *err = nil;
if (![self connectWithAddress4:address4 address6:address6 error:&err])
{
[self closeWithError:err];
}
}
/**
* This method is called if the DNS lookup fails.
* This method is executed on the socketQueue.
*
* Since the DNS lookup executed synchronously on a global concurrent queue,
* the original connection request may have already been cancelled or timed-out by the time this method is invoked.
* The lookupIndex tells us whether the lookup is still valid or not.
**/
- (void)lookup:(int)aStateIndex didFail:(NSError *)error
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
if (aStateIndex != stateIndex)
{
LogInfo(@"Ignoring lookup:didFail: - already disconnected");
// The connect operation has been cancelled.
// That is, socket was disconnected, or connection has already timed out.
return;
}
[self endConnectTimeout];
[self closeWithError:error];
}
- (BOOL)bindSocket:(int)socketFD toInterface:(NSData *)connectInterface error:(NSError **)errPtr
{
// Bind the socket to the desired interface (if needed)
if (connectInterface)
{
LogVerbose(@"Binding socket...");
if ([[self class] portFromAddress:connectInterface] > 0)
{
// Since we're going to be binding to a specific port,
// we should turn on reuseaddr to allow us to override sockets in time_wait.
int reuseOn = 1;
setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
}
const struct sockaddr *interfaceAddr = (const struct sockaddr *)[connectInterface bytes];
int result = bind(socketFD, interfaceAddr, (socklen_t)[connectInterface length]);
if (result != 0)
{
if (errPtr)
*errPtr = [self errorWithErrno:errno reason:@"Error in bind() function"];
return NO;
}
}
return YES;
}
- (int)createSocket:(int)family connectInterface:(NSData *)connectInterface errPtr:(NSError **)errPtr
{
int socketFD = socket(family, SOCK_STREAM, 0);
if (socketFD == SOCKET_NULL)
{
if (errPtr)
*errPtr = [self errorWithErrno:errno reason:@"Error in socket() function"];
return socketFD;
}
if (![self bindSocket:socketFD toInterface:connectInterface error:errPtr])
{
[self closeSocket:socketFD];
return SOCKET_NULL;
}
// Prevent SIGPIPE signals
int nosigpipe = 1;
setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
return socketFD;
}
- (void)connectSocket:(int)socketFD address:(NSData *)address stateIndex:(int)aStateIndex
{
// If there already is a socket connected, we close socketFD and return
if (self.isConnected)
{
[self closeSocket:socketFD];
return;
}
// Start the connection process in a background queue
__weak GCDAsyncSocket *weakSelf = self;
dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalConcurrentQueue, ^{
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
int result = connect(socketFD, (const struct sockaddr *)[address bytes], (socklen_t)[address length]);
int err = errno;
__strong GCDAsyncSocket *strongSelf = weakSelf;
if (strongSelf == nil) return_from_block;
dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
if (strongSelf.isConnected)
{
[strongSelf closeSocket:socketFD];
return_from_block;
}
if (result == 0)
{
[self closeUnusedSocket:socketFD];
[strongSelf didConnect:aStateIndex];
}
else
{
[strongSelf closeSocket:socketFD];
// If there are no more sockets trying to connect, we inform the error to the delegate
if (strongSelf.socket4FD == SOCKET_NULL && strongSelf.socket6FD == SOCKET_NULL)
{
NSError *error = [strongSelf errorWithErrno:err reason:@"Error in connect() function"];
[strongSelf didNotConnect:aStateIndex error:error];
}
}
}});
#pragma clang diagnostic pop
});
LogVerbose(@"Connecting...");
}
- (void)closeSocket:(int)socketFD
{
if (socketFD != SOCKET_NULL &&
(socketFD == socket6FD || socketFD == socket4FD))
{
close(socketFD);
if (socketFD == socket4FD)
{
LogVerbose(@"close(socket4FD)");
socket4FD = SOCKET_NULL;
}
else if (socketFD == socket6FD)
{
LogVerbose(@"close(socket6FD)");
socket6FD = SOCKET_NULL;
}
}
}
- (void)closeUnusedSocket:(int)usedSocketFD
{
if (usedSocketFD != socket4FD)
{
[self closeSocket:socket4FD];
}
else if (usedSocketFD != socket6FD)
{
[self closeSocket:socket6FD];
}
}
- (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
LogVerbose(@"IPv4: %@:%hu", [[self class] hostFromAddress:address4], [[self class] portFromAddress:address4]);
LogVerbose(@"IPv6: %@:%hu", [[self class] hostFromAddress:address6], [[self class] portFromAddress:address6]);
// Determine socket type
BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO;
// Create and bind the sockets
if (address4)
{
LogVerbose(@"Creating IPv4 socket");
socket4FD = [self createSocket:AF_INET connectInterface:connectInterface4 errPtr:errPtr];
}
if (address6)
{
LogVerbose(@"Creating IPv6 socket");
socket6FD = [self createSocket:AF_INET6 connectInterface:connectInterface6 errPtr:errPtr];
}
if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL)
{
return NO;
}
int socketFD, alternateSocketFD;
NSData *address, *alternateAddress;
if ((preferIPv6 && socket6FD != SOCKET_NULL) || socket4FD == SOCKET_NULL)
{
socketFD = socket6FD;
alternateSocketFD = socket4FD;
address = address6;
alternateAddress = address4;
}
else
{
socketFD = socket4FD;
alternateSocketFD = socket6FD;
address = address4;
alternateAddress = address6;
}
int aStateIndex = stateIndex;
[self connectSocket:socketFD address:address stateIndex:aStateIndex];
if (alternateAddress)
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(alternateAddressDelay * NSEC_PER_SEC)), socketQueue, ^{
[self connectSocket:alternateSocketFD address:alternateAddress stateIndex:aStateIndex];
});
}
return YES;
}
- (BOOL)connectWithAddressUN:(NSData *)address error:(NSError **)errPtr
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
// Create the socket
int socketFD;
LogVerbose(@"Creating unix domain socket");
socketUN = socket(AF_UNIX, SOCK_STREAM, 0);
socketFD = socketUN;
if (socketFD == SOCKET_NULL)
{
if (errPtr)
*errPtr = [self errorWithErrno:errno reason:@"Error in socket() function"];
return NO;
}
// Bind the socket to the desired interface (if needed)
LogVerbose(@"Binding socket...");
int reuseOn = 1;
setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
// const struct sockaddr *interfaceAddr = (const struct sockaddr *)[address bytes];
//
// int result = bind(socketFD, interfaceAddr, (socklen_t)[address length]);
// if (result != 0)
// {
// if (errPtr)
// *errPtr = [self errnoErrorWithReason:@"Error in bind() function"];
//
// return NO;
// }
// Prevent SIGPIPE signals
int nosigpipe = 1;
setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
// Start the connection process in a background queue
int aStateIndex = stateIndex;
dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalConcurrentQueue, ^{
const struct sockaddr *addr = (const struct sockaddr *)[address bytes];
int result = connect(socketFD, addr, addr->sa_len);
if (result == 0)
{
dispatch_async(self->socketQueue, ^{ @autoreleasepool {
[self didConnect:aStateIndex];
}});
}
else
{
// TODO: Bad file descriptor
perror("connect");
NSError *error = [self errorWithErrno:errno reason:@"Error in connect() function"];
dispatch_async(self->socketQueue, ^{ @autoreleasepool {
[self didNotConnect:aStateIndex error:error];
}});
}
});
LogVerbose(@"Connecting...");
return YES;
}
- (void)didConnect:(int)aStateIndex
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
if (aStateIndex != stateIndex)
{
LogInfo(@"Ignoring didConnect, already disconnected");
// The connect operation has been cancelled.
// That is, socket was disconnected, or connection has already timed out.
return;
}
flags |= kConnected;
[self endConnectTimeout];
#if TARGET_OS_IPHONE
// The endConnectTimeout method executed above incremented the stateIndex.
aStateIndex = stateIndex;
#endif
// Setup read/write streams (as workaround for specific shortcomings in the iOS platform)
//
// Note:
// There may be configuration options that must be set by the delegate before opening the streams.
// The primary example is the kCFStreamNetworkServiceTypeVoIP flag, which only works on an unopened stream.
//
// Thus we wait until after the socket:didConnectToHost:port: delegate method has completed.
// This gives the delegate time to properly configure the streams if needed.
dispatch_block_t SetupStreamsPart1 = ^{
#if TARGET_OS_IPHONE
if (![self createReadAndWriteStream])
{
[self closeWithError:[self otherError:@"Error creating CFStreams"]];
return;
}
if (![self registerForStreamCallbacksIncludingReadWrite:NO])
{
[self closeWithError:[self otherError:@"Error in CFStreamSetClient"]];
return;
}
#endif
};
dispatch_block_t SetupStreamsPart2 = ^{
#if TARGET_OS_IPHONE
if (aStateIndex != self->stateIndex)
{
// The socket has been disconnected.
return;
}
if (![self addStreamsToRunLoop])
{
[self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]];
return;
}
if (![self openStreams])
{
[self closeWithError:[self otherError:@"Error creating CFStreams"]];
return;
}
#endif
};
// Notify delegate
NSString *host = [self connectedHost];
uint16_t port = [self connectedPort];
NSURL *url = [self connectedUrl];
__strong id theDelegate = delegate;
if (delegateQueue && host != nil && [theDelegate respondsToSelector:@selector(socket:didConnectToHost:port:)])
{
SetupStreamsPart1();
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate socket:self didConnectToHost:host port:port];
dispatch_async(self->socketQueue, ^{ @autoreleasepool {
SetupStreamsPart2();
}});
}});
}
else if (delegateQueue && url != nil && [theDelegate respondsToSelector:@selector(socket:didConnectToUrl:)])
{
SetupStreamsPart1();
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate socket:self didConnectToUrl:url];
dispatch_async(self->socketQueue, ^{ @autoreleasepool {
SetupStreamsPart2();
}});
}});
}
else
{
SetupStreamsPart1();
SetupStreamsPart2();
}
// Get the connected socket
int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
// Enable non-blocking IO on the socket
int result = fcntl(socketFD, F_SETFL, O_NONBLOCK);
if (result == -1)
{
NSString *errMsg = @"Error enabling non-blocking IO on socket (fcntl)";
[self closeWithError:[self otherError:errMsg]];
return;
}
// Setup our read/write sources
[self setupReadAndWriteSourcesForNewlyConnectedSocket:socketFD];
// Dequeue any pending read/write requests
[self maybeDequeueRead];
[self maybeDequeueWrite];
}
- (void)didNotConnect:(int)aStateIndex error:(NSError *)error
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
if (aStateIndex != stateIndex)
{
LogInfo(@"Ignoring didNotConnect, already disconnected");
// The connect operation has been cancelled.
// That is, socket was disconnected, or connection has already timed out.
return;
}
[self closeWithError:error];
}
- (void)startConnectTimeout:(NSTimeInterval)timeout
{
if (timeout >= 0.0)
{
connectTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
__weak GCDAsyncSocket *weakSelf = self;
dispatch_source_set_event_handler(connectTimer, ^{ @autoreleasepool {
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
__strong GCDAsyncSocket *strongSelf = weakSelf;
if (strongSelf == nil) return_from_block;
[strongSelf doConnectTimeout];
#pragma clang diagnostic pop
}});
#if !OS_OBJECT_USE_OBJC
dispatch_source_t theConnectTimer = connectTimer;
dispatch_source_set_cancel_handler(connectTimer, ^{
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
LogVerbose(@"dispatch_release(connectTimer)");
dispatch_release(theConnectTimer);
#pragma clang diagnostic pop
});
#endif
dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC));
dispatch_source_set_timer(connectTimer, tt, DISPATCH_TIME_FOREVER, 0);
dispatch_resume(connectTimer);
}
}
- (void)endConnectTimeout
{
LogTrace();
if (connectTimer)
{
dispatch_source_cancel(connectTimer);
connectTimer = NULL;
}
// Increment stateIndex.
// This will prevent us from processing results from any related background asynchronous operations.
//
// Note: This should be called from close method even if connectTimer is NULL.
// This is because one might disconnect a socket prior to a successful connection which had no timeout.
stateIndex++;
if (connectInterface4)
{
connectInterface4 = nil;
}
if (connectInterface6)
{
connectInterface6 = nil;
}
}
- (void)doConnectTimeout
{
LogTrace();
[self endConnectTimeout];
[self closeWithError:[self connectTimeoutError]];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Disconnecting
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)closeWithError:(NSError *)error
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
[self endConnectTimeout];
if (currentRead != nil) [self endCurrentRead];
if (currentWrite != nil) [self endCurrentWrite];
[readQueue removeAllObjects];
[writeQueue removeAllObjects];
[preBuffer reset];
#if TARGET_OS_IPHONE
{
if (readStream || writeStream)
{
[self removeStreamsFromRunLoop];
if (readStream)
{
CFReadStreamSetClient(readStream, kCFStreamEventNone, NULL, NULL);
CFReadStreamClose(readStream);
CFRelease(readStream);
readStream = NULL;
}
if (writeStream)
{
CFWriteStreamSetClient(writeStream, kCFStreamEventNone, NULL, NULL);
CFWriteStreamClose(writeStream);
CFRelease(writeStream);
writeStream = NULL;
}
}
}
#endif
[sslPreBuffer reset];
sslErrCode = lastSSLHandshakeError = noErr;
if (sslContext)
{
// Getting a linker error here about the SSLx() functions?
// You need to add the Security Framework to your application.
SSLClose(sslContext);
#if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080)
CFRelease(sslContext);
#else
SSLDisposeContext(sslContext);
#endif
sslContext = NULL;
}
// For some crazy reason (in my opinion), cancelling a dispatch source doesn't
// invoke the cancel handler if the dispatch source is paused.
// So we have to unpause the source if needed.
// This allows the cancel handler to be run, which in turn releases the source and closes the socket.
if (!accept4Source && !accept6Source && !acceptUNSource && !readSource && !writeSource)
{
LogVerbose(@"manually closing close");
if (socket4FD != SOCKET_NULL)
{
LogVerbose(@"close(socket4FD)");
close(socket4FD);
socket4FD = SOCKET_NULL;
}
if (socket6FD != SOCKET_NULL)
{
LogVerbose(@"close(socket6FD)");
close(socket6FD);
socket6FD = SOCKET_NULL;
}
if (socketUN != SOCKET_NULL)
{
LogVerbose(@"close(socketUN)");
close(socketUN);
socketUN = SOCKET_NULL;
unlink(socketUrl.path.fileSystemRepresentation);
socketUrl = nil;
}
}
else
{
if (accept4Source)
{
LogVerbose(@"dispatch_source_cancel(accept4Source)");
dispatch_source_cancel(accept4Source);
// We never suspend accept4Source
accept4Source = NULL;
}
if (accept6Source)
{
LogVerbose(@"dispatch_source_cancel(accept6Source)");
dispatch_source_cancel(accept6Source);
// We never suspend accept6Source
accept6Source = NULL;
}
if (acceptUNSource)
{
LogVerbose(@"dispatch_source_cancel(acceptUNSource)");
dispatch_source_cancel(acceptUNSource);
// We never suspend acceptUNSource
acceptUNSource = NULL;
}
if (readSource)
{
LogVerbose(@"dispatch_source_cancel(readSource)");
dispatch_source_cancel(readSource);
[self resumeReadSource];
readSource = NULL;
}
if (writeSource)
{
LogVerbose(@"dispatch_source_cancel(writeSource)");
dispatch_source_cancel(writeSource);
[self resumeWriteSource];
writeSource = NULL;
}
// The sockets will be closed by the cancel handlers of the corresponding source
socket4FD = SOCKET_NULL;
socket6FD = SOCKET_NULL;
socketUN = SOCKET_NULL;
}
// If the client has passed the connect/accept method, then the connection has at least begun.
// Notify delegate that it is now ending.
BOOL shouldCallDelegate = (flags & kSocketStarted) ? YES : NO;
BOOL isDeallocating = (flags & kDealloc) ? YES : NO;
// Clear stored socket info and all flags (config remains as is)
socketFDBytesAvailable = 0;
flags = 0;
sslWriteCachedLength = 0;
if (shouldCallDelegate)
{
__strong id theDelegate = delegate;
__strong id theSelf = isDeallocating ? nil : self;
if (delegateQueue && [theDelegate respondsToSelector: @selector(socketDidDisconnect:withError:)])
{
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate socketDidDisconnect:theSelf withError:error];
}});
}
}
}
- (void)disconnect
{
dispatch_block_t block = ^{ @autoreleasepool {
if (self->flags & kSocketStarted)
{
[self closeWithError:nil];
}
}};
// Synchronous disconnection, as documented in the header file
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
}
- (void)disconnectAfterReading
{
dispatch_async(socketQueue, ^{ @autoreleasepool {
if (self->flags & kSocketStarted)
{
self->flags |= (kForbidReadsWrites | kDisconnectAfterReads);
[self maybeClose];
}
}});
}
- (void)disconnectAfterWriting
{
dispatch_async(socketQueue, ^{ @autoreleasepool {
if (self->flags & kSocketStarted)
{
self->flags |= (kForbidReadsWrites | kDisconnectAfterWrites);
[self maybeClose];
}
}});
}
- (void)disconnectAfterReadingAndWriting
{
dispatch_async(socketQueue, ^{ @autoreleasepool {
if (self->flags & kSocketStarted)
{
self->flags |= (kForbidReadsWrites | kDisconnectAfterReads | kDisconnectAfterWrites);
[self maybeClose];
}
}});
}
/**
* Closes the socket if possible.
* That is, if all writes have completed, and we're set to disconnect after writing,
* or if all reads have completed, and we're set to disconnect after reading.
**/
- (void)maybeClose
{
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
BOOL shouldClose = NO;
if (flags & kDisconnectAfterReads)
{
if (([readQueue count] == 0) && (currentRead == nil))
{
if (flags & kDisconnectAfterWrites)
{
if (([writeQueue count] == 0) && (currentWrite == nil))
{
shouldClose = YES;
}
}
else
{
shouldClose = YES;
}
}
}
else if (flags & kDisconnectAfterWrites)
{
if (([writeQueue count] == 0) && (currentWrite == nil))
{
shouldClose = YES;
}
}
if (shouldClose)
{
[self closeWithError:nil];
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Errors
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (NSError *)badConfigError:(NSString *)errMsg
{
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadConfigError userInfo:userInfo];
}
- (NSError *)badParamError:(NSString *)errMsg
{
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadParamError userInfo:userInfo];
}
+ (NSError *)gaiError:(int)gai_error
{
NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding];
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo];
}
- (NSError *)errorWithErrno:(int)err reason:(NSString *)reason
{
NSString *errMsg = [NSString stringWithUTF8String:strerror(err)];
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg,
NSLocalizedFailureReasonErrorKey : reason};
return [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:userInfo];
}
- (NSError *)errnoError
{
NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)];
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo];
}
- (NSError *)sslError:(OSStatus)ssl_error
{
NSString *msg = @"Error code definition can be found in Apple's SecureTransport.h";
NSDictionary *userInfo = @{NSLocalizedRecoverySuggestionErrorKey : msg};
return [NSError errorWithDomain:@"kCFStreamErrorDomainSSL" code:ssl_error userInfo:userInfo];
}
- (NSError *)connectTimeoutError
{
NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketConnectTimeoutError",
@"GCDAsyncSocket", [NSBundle mainBundle],
@"Attempt to connect to host timed out", nil);
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketConnectTimeoutError userInfo:userInfo];
}
/**
* Returns a standard AsyncSocket maxed out error.
**/
- (NSError *)readMaxedOutError
{
NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketReadMaxedOutError",
@"GCDAsyncSocket", [NSBundle mainBundle],
@"Read operation reached set maximum length", nil);
NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg};
return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadMaxedOutError userInfo:info];
}
/**
* Returns a standard AsyncSocket write timeout error.
**/
- (NSError *)readTimeoutError
{
NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketReadTimeoutError",
@"GCDAsyncSocket", [NSBundle mainBundle],
@"Read operation timed out", nil);
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadTimeoutError userInfo:userInfo];
}
/**
* Returns a standard AsyncSocket write timeout error.
**/
- (NSError *)writeTimeoutError
{
NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketWriteTimeoutError",
@"GCDAsyncSocket", [NSBundle mainBundle],
@"Write operation timed out", nil);
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketWriteTimeoutError userInfo:userInfo];
}
- (NSError *)connectionClosedError
{
NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketClosedError",
@"GCDAsyncSocket", [NSBundle mainBundle],
@"Socket closed by remote peer", nil);
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketClosedError userInfo:userInfo];
}
- (NSError *)otherError:(NSString *)errMsg
{
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketOtherError userInfo:userInfo];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Diagnostics
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)isDisconnected
{
__block BOOL result = NO;
dispatch_block_t block = ^{
result = (self->flags & kSocketStarted) ? NO : YES;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (BOOL)isConnected
{
__block BOOL result = NO;
dispatch_block_t block = ^{
result = (self->flags & kConnected) ? YES : NO;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (NSString *)connectedHost
{
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
if (socket4FD != SOCKET_NULL)
return [self connectedHostFromSocket4:socket4FD];
if (socket6FD != SOCKET_NULL)
return [self connectedHostFromSocket6:socket6FD];
return nil;
}
else
{
__block NSString *result = nil;
dispatch_sync(socketQueue, ^{ @autoreleasepool {
if (self->socket4FD != SOCKET_NULL)
result = [self connectedHostFromSocket4:self->socket4FD];
else if (self->socket6FD != SOCKET_NULL)
result = [self connectedHostFromSocket6:self->socket6FD];
}});
return result;
}
}
- (uint16_t)connectedPort
{
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
if (socket4FD != SOCKET_NULL)
return [self connectedPortFromSocket4:socket4FD];
if (socket6FD != SOCKET_NULL)
return [self connectedPortFromSocket6:socket6FD];
return 0;
}
else
{
__block uint16_t result = 0;
dispatch_sync(socketQueue, ^{
// No need for autorelease pool
if (self->socket4FD != SOCKET_NULL)
result = [self connectedPortFromSocket4:self->socket4FD];
else if (self->socket6FD != SOCKET_NULL)
result = [self connectedPortFromSocket6:self->socket6FD];
});
return result;
}
}
- (NSURL *)connectedUrl
{
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
if (socketUN != SOCKET_NULL)
return [self connectedUrlFromSocketUN:socketUN];
return nil;
}
else
{
__block NSURL *result = nil;
dispatch_sync(socketQueue, ^{ @autoreleasepool {
if (self->socketUN != SOCKET_NULL)
result = [self connectedUrlFromSocketUN:self->socketUN];
}});
return result;
}
}
- (NSString *)localHost
{
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
if (socket4FD != SOCKET_NULL)
return [self localHostFromSocket4:socket4FD];
if (socket6FD != SOCKET_NULL)
return [self localHostFromSocket6:socket6FD];
return nil;
}
else
{
__block NSString *result = nil;
dispatch_sync(socketQueue, ^{ @autoreleasepool {
if (self->socket4FD != SOCKET_NULL)
result = [self localHostFromSocket4:self->socket4FD];
else if (self->socket6FD != SOCKET_NULL)
result = [self localHostFromSocket6:self->socket6FD];
}});
return result;
}
}
- (uint16_t)localPort
{
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
if (socket4FD != SOCKET_NULL)
return [self localPortFromSocket4:socket4FD];
if (socket6FD != SOCKET_NULL)
return [self localPortFromSocket6:socket6FD];
return 0;
}
else
{
__block uint16_t result = 0;
dispatch_sync(socketQueue, ^{
// No need for autorelease pool
if (self->socket4FD != SOCKET_NULL)
result = [self localPortFromSocket4:self->socket4FD];
else if (self->socket6FD != SOCKET_NULL)
result = [self localPortFromSocket6:self->socket6FD];
});
return result;
}
}
- (NSString *)connectedHost4
{
if (socket4FD != SOCKET_NULL)
return [self connectedHostFromSocket4:socket4FD];
return nil;
}
- (NSString *)connectedHost6
{
if (socket6FD != SOCKET_NULL)
return [self connectedHostFromSocket6:socket6FD];
return nil;
}
- (uint16_t)connectedPort4
{
if (socket4FD != SOCKET_NULL)
return [self connectedPortFromSocket4:socket4FD];
return 0;
}
- (uint16_t)connectedPort6
{
if (socket6FD != SOCKET_NULL)
return [self connectedPortFromSocket6:socket6FD];
return 0;
}
- (NSString *)localHost4
{
if (socket4FD != SOCKET_NULL)
return [self localHostFromSocket4:socket4FD];
return nil;
}
- (NSString *)localHost6
{
if (socket6FD != SOCKET_NULL)
return [self localHostFromSocket6:socket6FD];
return nil;
}
- (uint16_t)localPort4
{
if (socket4FD != SOCKET_NULL)
return [self localPortFromSocket4:socket4FD];
return 0;
}
- (uint16_t)localPort6
{
if (socket6FD != SOCKET_NULL)
return [self localPortFromSocket6:socket6FD];
return 0;
}
- (NSString *)connectedHostFromSocket4:(int)socketFD
{
struct sockaddr_in sockaddr4;
socklen_t sockaddr4len = sizeof(sockaddr4);
if (getpeername(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
{
return nil;
}
return [[self class] hostFromSockaddr4:&sockaddr4];
}
- (NSString *)connectedHostFromSocket6:(int)socketFD
{
struct sockaddr_in6 sockaddr6;
socklen_t sockaddr6len = sizeof(sockaddr6);
if (getpeername(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
{
return nil;
}
return [[self class] hostFromSockaddr6:&sockaddr6];
}
- (uint16_t)connectedPortFromSocket4:(int)socketFD
{
struct sockaddr_in sockaddr4;
socklen_t sockaddr4len = sizeof(sockaddr4);
if (getpeername(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
{
return 0;
}
return [[self class] portFromSockaddr4:&sockaddr4];
}
- (uint16_t)connectedPortFromSocket6:(int)socketFD
{
struct sockaddr_in6 sockaddr6;
socklen_t sockaddr6len = sizeof(sockaddr6);
if (getpeername(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
{
return 0;
}
return [[self class] portFromSockaddr6:&sockaddr6];
}
- (NSURL *)connectedUrlFromSocketUN:(int)socketFD
{
struct sockaddr_un sockaddr;
socklen_t sockaddrlen = sizeof(sockaddr);
if (getpeername(socketFD, (struct sockaddr *)&sockaddr, &sockaddrlen) < 0)
{
return 0;
}
return [[self class] urlFromSockaddrUN:&sockaddr];
}
- (NSString *)localHostFromSocket4:(int)socketFD
{
struct sockaddr_in sockaddr4;
socklen_t sockaddr4len = sizeof(sockaddr4);
if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
{
return nil;
}
return [[self class] hostFromSockaddr4:&sockaddr4];
}
- (NSString *)localHostFromSocket6:(int)socketFD
{
struct sockaddr_in6 sockaddr6;
socklen_t sockaddr6len = sizeof(sockaddr6);
if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
{
return nil;
}
return [[self class] hostFromSockaddr6:&sockaddr6];
}
- (uint16_t)localPortFromSocket4:(int)socketFD
{
struct sockaddr_in sockaddr4;
socklen_t sockaddr4len = sizeof(sockaddr4);
if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
{
return 0;
}
return [[self class] portFromSockaddr4:&sockaddr4];
}
- (uint16_t)localPortFromSocket6:(int)socketFD
{
struct sockaddr_in6 sockaddr6;
socklen_t sockaddr6len = sizeof(sockaddr6);
if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
{
return 0;
}
return [[self class] portFromSockaddr6:&sockaddr6];
}
- (NSData *)connectedAddress
{
__block NSData *result = nil;
dispatch_block_t block = ^{
if (self->socket4FD != SOCKET_NULL)
{
struct sockaddr_in sockaddr4;
socklen_t sockaddr4len = sizeof(sockaddr4);
if (getpeername(self->socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0)
{
result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len];
}
}
if (self->socket6FD != SOCKET_NULL)
{
struct sockaddr_in6 sockaddr6;
socklen_t sockaddr6len = sizeof(sockaddr6);
if (getpeername(self->socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0)
{
result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len];
}
}
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (NSData *)localAddress
{
__block NSData *result = nil;
dispatch_block_t block = ^{
if (self->socket4FD != SOCKET_NULL)
{
struct sockaddr_in sockaddr4;
socklen_t sockaddr4len = sizeof(sockaddr4);
if (getsockname(self->socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0)
{
result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len];
}
}
if (self->socket6FD != SOCKET_NULL)
{
struct sockaddr_in6 sockaddr6;
socklen_t sockaddr6len = sizeof(sockaddr6);
if (getsockname(self->socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0)
{
result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len];
}
}
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (BOOL)isIPv4
{
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
return (socket4FD != SOCKET_NULL);
}
else
{
__block BOOL result = NO;
dispatch_sync(socketQueue, ^{
result = (self->socket4FD != SOCKET_NULL);
});
return result;
}
}
- (BOOL)isIPv6
{
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
return (socket6FD != SOCKET_NULL);
}
else
{
__block BOOL result = NO;
dispatch_sync(socketQueue, ^{
result = (self->socket6FD != SOCKET_NULL);
});
return result;
}
}
- (BOOL)isSecure
{
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
return (flags & kSocketSecure) ? YES : NO;
}
else
{
__block BOOL result;
dispatch_sync(socketQueue, ^{
result = (self->flags & kSocketSecure) ? YES : NO;
});
return result;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Utilities
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Finds the address of an interface description.
* An inteface description may be an interface name (en0, en1, lo0) or corresponding IP (192.168.4.34).
*
* The interface description may optionally contain a port number at the end, separated by a colon.
* If a non-zero port parameter is provided, any port number in the interface description is ignored.
*
* The returned value is a 'struct sockaddr' wrapped in an NSMutableData object.
**/
- (void)getInterfaceAddress4:(NSMutableData **)interfaceAddr4Ptr
address6:(NSMutableData **)interfaceAddr6Ptr
fromDescription:(NSString *)interfaceDescription
port:(uint16_t)port
{
NSMutableData *addr4 = nil;
NSMutableData *addr6 = nil;
NSString *interface = nil;
NSArray *components = [interfaceDescription componentsSeparatedByString:@":"];
if ([components count] > 0)
{
NSString *temp = [components objectAtIndex:0];
if ([temp length] > 0)
{
interface = temp;
}
}
if ([components count] > 1 && port == 0)
{
NSString *temp = [components objectAtIndex:1];
long portL = strtol([temp UTF8String], NULL, 10);
if (portL > 0 && portL <= UINT16_MAX)
{
port = (uint16_t)portL;
}
}
if (interface == nil)
{
// ANY address
struct sockaddr_in sockaddr4;
memset(&sockaddr4, 0, sizeof(sockaddr4));
sockaddr4.sin_len = sizeof(sockaddr4);
sockaddr4.sin_family = AF_INET;
sockaddr4.sin_port = htons(port);
sockaddr4.sin_addr.s_addr = htonl(INADDR_ANY);
struct sockaddr_in6 sockaddr6;
memset(&sockaddr6, 0, sizeof(sockaddr6));
sockaddr6.sin6_len = sizeof(sockaddr6);
sockaddr6.sin6_family = AF_INET6;
sockaddr6.sin6_port = htons(port);
sockaddr6.sin6_addr = in6addr_any;
addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)];
addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)];
}
else if ([interface isEqualToString:@"localhost"] || [interface isEqualToString:@"loopback"])
{
// LOOPBACK address
struct sockaddr_in sockaddr4;
memset(&sockaddr4, 0, sizeof(sockaddr4));
sockaddr4.sin_len = sizeof(sockaddr4);
sockaddr4.sin_family = AF_INET;
sockaddr4.sin_port = htons(port);
sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
struct sockaddr_in6 sockaddr6;
memset(&sockaddr6, 0, sizeof(sockaddr6));
sockaddr6.sin6_len = sizeof(sockaddr6);
sockaddr6.sin6_family = AF_INET6;
sockaddr6.sin6_port = htons(port);
sockaddr6.sin6_addr = in6addr_loopback;
addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)];
addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)];
}
else
{
const char *iface = [interface UTF8String];
struct ifaddrs *addrs;
const struct ifaddrs *cursor;
if ((getifaddrs(&addrs) == 0))
{
cursor = addrs;
while (cursor != NULL)
{
if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET))
{
// IPv4
struct sockaddr_in nativeAddr4;
memcpy(&nativeAddr4, cursor->ifa_addr, sizeof(nativeAddr4));
if (strcmp(cursor->ifa_name, iface) == 0)
{
// Name match
nativeAddr4.sin_port = htons(port);
addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
}
else
{
char ip[INET_ADDRSTRLEN];
const char *conversion = inet_ntop(AF_INET, &nativeAddr4.sin_addr, ip, sizeof(ip));
if ((conversion != NULL) && (strcmp(ip, iface) == 0))
{
// IP match
nativeAddr4.sin_port = htons(port);
addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
}
}
}
else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6))
{
// IPv6
struct sockaddr_in6 nativeAddr6;
memcpy(&nativeAddr6, cursor->ifa_addr, sizeof(nativeAddr6));
if (strcmp(cursor->ifa_name, iface) == 0)
{
// Name match
nativeAddr6.sin6_port = htons(port);
addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
}
else
{
char ip[INET6_ADDRSTRLEN];
const char *conversion = inet_ntop(AF_INET6, &nativeAddr6.sin6_addr, ip, sizeof(ip));
if ((conversion != NULL) && (strcmp(ip, iface) == 0))
{
// IP match
nativeAddr6.sin6_port = htons(port);
addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
}
}
}
cursor = cursor->ifa_next;
}
freeifaddrs(addrs);
}
}
if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4;
if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6;
}
- (NSData *)getInterfaceAddressFromUrl:(NSURL *)url
{
NSString *path = url.path;
if (path.length == 0) {
return nil;
}
struct sockaddr_un nativeAddr;
nativeAddr.sun_family = AF_UNIX;
strlcpy(nativeAddr.sun_path, path.fileSystemRepresentation, sizeof(nativeAddr.sun_path));
nativeAddr.sun_len = (unsigned char)SUN_LEN(&nativeAddr);
NSData *interface = [NSData dataWithBytes:&nativeAddr length:sizeof(struct sockaddr_un)];
return interface;
}
- (void)setupReadAndWriteSourcesForNewlyConnectedSocket:(int)socketFD
{
readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socketFD, 0, socketQueue);
writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socketFD, 0, socketQueue);
// Setup event handlers
__weak GCDAsyncSocket *weakSelf = self;
dispatch_source_set_event_handler(readSource, ^{ @autoreleasepool {
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
__strong GCDAsyncSocket *strongSelf = weakSelf;
if (strongSelf == nil) return_from_block;
LogVerbose(@"readEventBlock");
strongSelf->socketFDBytesAvailable = dispatch_source_get_data(strongSelf->readSource);
LogVerbose(@"socketFDBytesAvailable: %lu", strongSelf->socketFDBytesAvailable);
if (strongSelf->socketFDBytesAvailable > 0)
[strongSelf doReadData];
else
[strongSelf doReadEOF];
#pragma clang diagnostic pop
}});
dispatch_source_set_event_handler(writeSource, ^{ @autoreleasepool {
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
__strong GCDAsyncSocket *strongSelf = weakSelf;
if (strongSelf == nil) return_from_block;
LogVerbose(@"writeEventBlock");
strongSelf->flags |= kSocketCanAcceptBytes;
[strongSelf doWriteData];
#pragma clang diagnostic pop
}});
// Setup cancel handlers
__block int socketFDRefCount = 2;
#if !OS_OBJECT_USE_OBJC
dispatch_source_t theReadSource = readSource;
dispatch_source_t theWriteSource = writeSource;
#endif
dispatch_source_set_cancel_handler(readSource, ^{
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
LogVerbose(@"readCancelBlock");
#if !OS_OBJECT_USE_OBJC
LogVerbose(@"dispatch_release(readSource)");
dispatch_release(theReadSource);
#endif
if (--socketFDRefCount == 0)
{
LogVerbose(@"close(socketFD)");
close(socketFD);
}
#pragma clang diagnostic pop
});
dispatch_source_set_cancel_handler(writeSource, ^{
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
LogVerbose(@"writeCancelBlock");
#if !OS_OBJECT_USE_OBJC
LogVerbose(@"dispatch_release(writeSource)");
dispatch_release(theWriteSource);
#endif
if (--socketFDRefCount == 0)
{
LogVerbose(@"close(socketFD)");
close(socketFD);
}
#pragma clang diagnostic pop
});
// We will not be able to read until data arrives.
// But we should be able to write immediately.
socketFDBytesAvailable = 0;
flags &= ~kReadSourceSuspended;
LogVerbose(@"dispatch_resume(readSource)");
dispatch_resume(readSource);
flags |= kSocketCanAcceptBytes;
flags |= kWriteSourceSuspended;
}
- (BOOL)usingCFStreamForTLS
{
#if TARGET_OS_IPHONE
if ((flags & kSocketSecure) && (flags & kUsingCFStreamForTLS))
{
// The startTLS method was given the GCDAsyncSocketUseCFStreamForTLS flag.
return YES;
}
#endif
return NO;
}
- (BOOL)usingSecureTransportForTLS
{
// Invoking this method is equivalent to ![self usingCFStreamForTLS] (just more readable)
#if TARGET_OS_IPHONE
if ((flags & kSocketSecure) && (flags & kUsingCFStreamForTLS))
{
// The startTLS method was given the GCDAsyncSocketUseCFStreamForTLS flag.
return NO;
}
#endif
return YES;
}
- (void)suspendReadSource
{
if (!(flags & kReadSourceSuspended))
{
LogVerbose(@"dispatch_suspend(readSource)");
dispatch_suspend(readSource);
flags |= kReadSourceSuspended;
}
}
- (void)resumeReadSource
{
if (flags & kReadSourceSuspended)
{
LogVerbose(@"dispatch_resume(readSource)");
dispatch_resume(readSource);
flags &= ~kReadSourceSuspended;
}
}
- (void)suspendWriteSource
{
if (!(flags & kWriteSourceSuspended))
{
LogVerbose(@"dispatch_suspend(writeSource)");
dispatch_suspend(writeSource);
flags |= kWriteSourceSuspended;
}
}
- (void)resumeWriteSource
{
if (flags & kWriteSourceSuspended)
{
LogVerbose(@"dispatch_resume(writeSource)");
dispatch_resume(writeSource);
flags &= ~kWriteSourceSuspended;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Reading
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag
{
[self readDataWithTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag];
}
- (void)readDataWithTimeout:(NSTimeInterval)timeout
buffer:(NSMutableData *)buffer
bufferOffset:(NSUInteger)offset
tag:(long)tag
{
[self readDataWithTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag];
}
- (void)readDataWithTimeout:(NSTimeInterval)timeout
buffer:(NSMutableData *)buffer
bufferOffset:(NSUInteger)offset
maxLength:(NSUInteger)length
tag:(long)tag
{
if (offset > [buffer length]) {
LogWarn(@"Cannot read: offset > [buffer length]");
return;
}
GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer
startOffset:offset
maxLength:length
timeout:timeout
readLength:0
terminator:nil
tag:tag];
dispatch_async(socketQueue, ^{ @autoreleasepool {
LogTrace();
if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites))
{
[self->readQueue addObject:packet];
[self maybeDequeueRead];
}
}});
// Do not rely on the block being run in order to release the packet,
// as the queue might get released without the block completing.
}
- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag
{
[self readDataToLength:length withTimeout:timeout buffer:nil bufferOffset:0 tag:tag];
}
- (void)readDataToLength:(NSUInteger)length
withTimeout:(NSTimeInterval)timeout
buffer:(NSMutableData *)buffer
bufferOffset:(NSUInteger)offset
tag:(long)tag
{
if (length == 0) {
LogWarn(@"Cannot read: length == 0");
return;
}
if (offset > [buffer length]) {
LogWarn(@"Cannot read: offset > [buffer length]");
return;
}
GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer
startOffset:offset
maxLength:0
timeout:timeout
readLength:length
terminator:nil
tag:tag];
dispatch_async(socketQueue, ^{ @autoreleasepool {
LogTrace();
if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites))
{
[self->readQueue addObject:packet];
[self maybeDequeueRead];
}
}});
// Do not rely on the block being run in order to release the packet,
// as the queue might get released without the block completing.
}
- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag
{
[self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag];
}
- (void)readDataToData:(NSData *)data
withTimeout:(NSTimeInterval)timeout
buffer:(NSMutableData *)buffer
bufferOffset:(NSUInteger)offset
tag:(long)tag
{
[self readDataToData:data withTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag];
}
- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag
{
[self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:length tag:tag];
}
- (void)readDataToData:(NSData *)data
withTimeout:(NSTimeInterval)timeout
buffer:(NSMutableData *)buffer
bufferOffset:(NSUInteger)offset
maxLength:(NSUInteger)maxLength
tag:(long)tag
{
if ([data length] == 0) {
LogWarn(@"Cannot read: [data length] == 0");
return;
}
if (offset > [buffer length]) {
LogWarn(@"Cannot read: offset > [buffer length]");
return;
}
if (maxLength > 0 && maxLength < [data length]) {
LogWarn(@"Cannot read: maxLength > 0 && maxLength < [data length]");
return;
}
GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer
startOffset:offset
maxLength:maxLength
timeout:timeout
readLength:0
terminator:data
tag:tag];
dispatch_async(socketQueue, ^{ @autoreleasepool {
LogTrace();
if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites))
{
[self->readQueue addObject:packet];
[self maybeDequeueRead];
}
}});
// Do not rely on the block being run in order to release the packet,
// as the queue might get released without the block completing.
}
- (float)progressOfReadReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr
{
__block float result = 0.0F;
dispatch_block_t block = ^{
if (!self->currentRead || ![self->currentRead isKindOfClass:[GCDAsyncReadPacket class]])
{
// We're not reading anything right now.
if (tagPtr != NULL) *tagPtr = 0;
if (donePtr != NULL) *donePtr = 0;
if (totalPtr != NULL) *totalPtr = 0;
result = NAN;
}
else
{
// It's only possible to know the progress of our read if we're reading to a certain length.
// If we're reading to data, we of course have no idea when the data will arrive.
// If we're reading to timeout, then we have no idea when the next chunk of data will arrive.
NSUInteger done = self->currentRead->bytesDone;
NSUInteger total = self->currentRead->readLength;
if (tagPtr != NULL) *tagPtr = self->currentRead->tag;
if (donePtr != NULL) *donePtr = done;
if (totalPtr != NULL) *totalPtr = total;
if (total > 0)
result = (float)done / (float)total;
else
result = 1.0F;
}
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
/**
* This method starts a new read, if needed.
*
* It is called when:
* - a user requests a read
* - after a read request has finished (to handle the next request)
* - immediately after the socket opens to handle any pending requests
*
* This method also handles auto-disconnect post read/write completion.
**/
- (void)maybeDequeueRead
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
// If we're not currently processing a read AND we have an available read stream
if ((currentRead == nil) && (flags & kConnected))
{
if ([readQueue count] > 0)
{
// Dequeue the next object in the write queue
currentRead = [readQueue objectAtIndex:0];
[readQueue removeObjectAtIndex:0];
if ([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]])
{
LogVerbose(@"Dequeued GCDAsyncSpecialPacket");
// Attempt to start TLS
flags |= kStartingReadTLS;
// This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set
[self maybeStartTLS];
}
else
{
LogVerbose(@"Dequeued GCDAsyncReadPacket");
// Setup read timer (if needed)
[self setupReadTimerWithTimeout:currentRead->timeout];
// Immediately read, if possible
[self doReadData];
}
}
else if (flags & kDisconnectAfterReads)
{
if (flags & kDisconnectAfterWrites)
{
if (([writeQueue count] == 0) && (currentWrite == nil))
{
[self closeWithError:nil];
}
}
else
{
[self closeWithError:nil];
}
}
else if (flags & kSocketSecure)
{
[self flushSSLBuffers];
// Edge case:
//
// We just drained all data from the ssl buffers,
// and all known data from the socket (socketFDBytesAvailable).
//
// If we didn't get any data from this process,
// then we may have reached the end of the TCP stream.
//
// Be sure callbacks are enabled so we're notified about a disconnection.
if ([preBuffer availableBytes] == 0)
{
if ([self usingCFStreamForTLS]) {
// Callbacks never disabled
}
else {
[self resumeReadSource];
}
}
}
}
}
- (void)flushSSLBuffers
{
LogTrace();
NSAssert((flags & kSocketSecure), @"Cannot flush ssl buffers on non-secure socket");
if ([preBuffer availableBytes] > 0)
{
// Only flush the ssl buffers if the prebuffer is empty.
// This is to avoid growing the prebuffer inifinitely large.
return;
}
#if TARGET_OS_IPHONE
if ([self usingCFStreamForTLS])
{
if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream))
{
LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD);
CFIndex defaultBytesToRead = (1024 * 4);
[preBuffer ensureCapacityForWrite:defaultBytesToRead];
uint8_t *buffer = [preBuffer writeBuffer];
CFIndex result = CFReadStreamRead(readStream, buffer, defaultBytesToRead);
LogVerbose(@"%@ - CFReadStreamRead(): result = %i", THIS_METHOD, (int)result);
if (result > 0)
{
[preBuffer didWrite:result];
}
flags &= ~kSecureSocketHasBytesAvailable;
}
return;
}
#endif
__block NSUInteger estimatedBytesAvailable = 0;
dispatch_block_t updateEstimatedBytesAvailable = ^{
// Figure out if there is any data available to be read
//
// socketFDBytesAvailable <- Number of encrypted bytes we haven't read from the bsd socket
// [sslPreBuffer availableBytes] <- Number of encrypted bytes we've buffered from bsd socket
// sslInternalBufSize <- Number of decrypted bytes SecureTransport has buffered
//
// We call the variable "estimated" because we don't know how many decrypted bytes we'll get
// from the encrypted bytes in the sslPreBuffer.
// However, we do know this is an upper bound on the estimation.
estimatedBytesAvailable = self->socketFDBytesAvailable + [self->sslPreBuffer availableBytes];
size_t sslInternalBufSize = 0;
SSLGetBufferedReadSize(self->sslContext, &sslInternalBufSize);
estimatedBytesAvailable += sslInternalBufSize;
};
updateEstimatedBytesAvailable();
if (estimatedBytesAvailable > 0)
{
LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD);
BOOL done = NO;
do
{
LogVerbose(@"%@ - estimatedBytesAvailable = %lu", THIS_METHOD, (unsigned long)estimatedBytesAvailable);
// Make sure there's enough room in the prebuffer
[preBuffer ensureCapacityForWrite:estimatedBytesAvailable];
// Read data into prebuffer
uint8_t *buffer = [preBuffer writeBuffer];
size_t bytesRead = 0;
OSStatus result = SSLRead(sslContext, buffer, (size_t)estimatedBytesAvailable, &bytesRead);
LogVerbose(@"%@ - read from secure socket = %u", THIS_METHOD, (unsigned)bytesRead);
if (bytesRead > 0)
{
[preBuffer didWrite:bytesRead];
}
LogVerbose(@"%@ - prebuffer.length = %zu", THIS_METHOD, [preBuffer availableBytes]);
if (result != noErr)
{
done = YES;
}
else
{
updateEstimatedBytesAvailable();
}
} while (!done && estimatedBytesAvailable > 0);
}
}
- (void)doReadData
{
LogTrace();
// This method is called on the socketQueue.
// It might be called directly, or via the readSource when data is available to be read.
if ((currentRead == nil) || (flags & kReadsPaused))
{
LogVerbose(@"No currentRead or kReadsPaused");
// Unable to read at this time
if (flags & kSocketSecure)
{
// Here's the situation:
//
// We have an established secure connection.
// There may not be a currentRead, but there might be encrypted data sitting around for us.
// When the user does get around to issuing a read, that encrypted data will need to be decrypted.
//
// So why make the user wait?
// We might as well get a head start on decrypting some data now.
//
// The other reason we do this has to do with detecting a socket disconnection.
// The SSL/TLS protocol has it's own disconnection handshake.
// So when a secure socket is closed, a "goodbye" packet comes across the wire.
// We want to make sure we read the "goodbye" packet so we can properly detect the TCP disconnection.
[self flushSSLBuffers];
}
if ([self usingCFStreamForTLS])
{
// CFReadStream only fires once when there is available data.
// It won't fire again until we've invoked CFReadStreamRead.
}
else
{
// If the readSource is firing, we need to pause it
// or else it will continue to fire over and over again.
//
// If the readSource is not firing,
// we want it to continue monitoring the socket.
if (socketFDBytesAvailable > 0)
{
[self suspendReadSource];
}
}
return;
}
BOOL hasBytesAvailable = NO;
unsigned long estimatedBytesAvailable = 0;
if ([self usingCFStreamForTLS])
{
#if TARGET_OS_IPHONE
// Requested CFStream, rather than SecureTransport, for TLS (via GCDAsyncSocketUseCFStreamForTLS)
estimatedBytesAvailable = 0;
if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream))
hasBytesAvailable = YES;
else
hasBytesAvailable = NO;
#endif
}
else
{
estimatedBytesAvailable = socketFDBytesAvailable;
if (flags & kSocketSecure)
{
// There are 2 buffers to be aware of here.
//
// We are using SecureTransport, a TLS/SSL security layer which sits atop TCP.
// We issue a read to the SecureTranport API, which in turn issues a read to our SSLReadFunction.
// Our SSLReadFunction then reads from the BSD socket and returns the encrypted data to SecureTransport.
// SecureTransport then decrypts the data, and finally returns the decrypted data back to us.
//
// The first buffer is one we create.
// SecureTransport often requests small amounts of data.
// This has to do with the encypted packets that are coming across the TCP stream.
// But it's non-optimal to do a bunch of small reads from the BSD socket.
// So our SSLReadFunction reads all available data from the socket (optimizing the sys call)
// and may store excess in the sslPreBuffer.
estimatedBytesAvailable += [sslPreBuffer availableBytes];
// The second buffer is within SecureTransport.
// As mentioned earlier, there are encrypted packets coming across the TCP stream.
// SecureTransport needs the entire packet to decrypt it.
// But if the entire packet produces X bytes of decrypted data,
// and we only asked SecureTransport for X/2 bytes of data,
// it must store the extra X/2 bytes of decrypted data for the next read.
//
// The SSLGetBufferedReadSize function will tell us the size of this internal buffer.
// From the documentation:
//
// "This function does not block or cause any low-level read operations to occur."
size_t sslInternalBufSize = 0;
SSLGetBufferedReadSize(sslContext, &sslInternalBufSize);
estimatedBytesAvailable += sslInternalBufSize;
}
hasBytesAvailable = (estimatedBytesAvailable > 0);
}
if ((hasBytesAvailable == NO) && ([preBuffer availableBytes] == 0))
{
LogVerbose(@"No data available to read...");
// No data available to read.
if (![self usingCFStreamForTLS])
{
// Need to wait for readSource to fire and notify us of
// available data in the socket's internal read buffer.
[self resumeReadSource];
}
return;
}
if (flags & kStartingReadTLS)
{
LogVerbose(@"Waiting for SSL/TLS handshake to complete");
// The readQueue is waiting for SSL/TLS handshake to complete.
if (flags & kStartingWriteTLS)
{
if ([self usingSecureTransportForTLS] && lastSSLHandshakeError == errSSLWouldBlock)
{
// We are in the process of a SSL Handshake.
// We were waiting for incoming data which has just arrived.
[self ssl_continueSSLHandshake];
}
}
else
{
// We are still waiting for the writeQueue to drain and start the SSL/TLS process.
// We now know data is available to read.
if (![self usingCFStreamForTLS])
{
// Suspend the read source or else it will continue to fire nonstop.
[self suspendReadSource];
}
}
return;
}
BOOL done = NO; // Completed read operation
NSError *error = nil; // Error occurred
NSUInteger totalBytesReadForCurrentRead = 0;
//
// STEP 1 - READ FROM PREBUFFER
//
if ([preBuffer availableBytes] > 0)
{
// There are 3 types of read packets:
//
// 1) Read all available data.
// 2) Read a specific length of data.
// 3) Read up to a particular terminator.
NSUInteger bytesToCopy;
if (currentRead->term != nil)
{
// Read type #3 - read up to a terminator
bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done];
}
else
{
// Read type #1 or #2
bytesToCopy = [currentRead readLengthForNonTermWithHint:[preBuffer availableBytes]];
}
// Make sure we have enough room in the buffer for our read.
[currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy];
// Copy bytes from prebuffer into packet buffer
uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset +
currentRead->bytesDone;
memcpy(buffer, [preBuffer readBuffer], bytesToCopy);
// Remove the copied bytes from the preBuffer
[preBuffer didRead:bytesToCopy];
LogVerbose(@"copied(%lu) preBufferLength(%zu)", (unsigned long)bytesToCopy, [preBuffer availableBytes]);
// Update totals
currentRead->bytesDone += bytesToCopy;
totalBytesReadForCurrentRead += bytesToCopy;
// Check to see if the read operation is done
if (currentRead->readLength > 0)
{
// Read type #2 - read a specific length of data
done = (currentRead->bytesDone == currentRead->readLength);
}
else if (currentRead->term != nil)
{
// Read type #3 - read up to a terminator
// Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method
if (!done && currentRead->maxLength > 0)
{
// We're not done and there's a set maxLength.
// Have we reached that maxLength yet?
if (currentRead->bytesDone >= currentRead->maxLength)
{
error = [self readMaxedOutError];
}
}
}
else
{
// Read type #1 - read all available data
//
// We're done as soon as
// - we've read all available data (in prebuffer and socket)
// - we've read the maxLength of read packet.
done = ((currentRead->maxLength > 0) && (currentRead->bytesDone == currentRead->maxLength));
}
}
//
// STEP 2 - READ FROM SOCKET
//
BOOL socketEOF = (flags & kSocketHasReadEOF) ? YES : NO; // Nothing more to read via socket (end of file)
BOOL waiting = !done && !error && !socketEOF && !hasBytesAvailable; // Ran out of data, waiting for more
if (!done && !error && !socketEOF && hasBytesAvailable)
{
NSAssert(([preBuffer availableBytes] == 0), @"Invalid logic");
BOOL readIntoPreBuffer = NO;
uint8_t *buffer = NULL;
size_t bytesRead = 0;
if (flags & kSocketSecure)
{
if ([self usingCFStreamForTLS])
{
#if TARGET_OS_IPHONE
// Using CFStream, rather than SecureTransport, for TLS
NSUInteger defaultReadLength = (1024 * 32);
NSUInteger bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength
shouldPreBuffer:&readIntoPreBuffer];
// Make sure we have enough room in the buffer for our read.
//
// We are either reading directly into the currentRead->buffer,
// or we're reading into the temporary preBuffer.
if (readIntoPreBuffer)
{
[preBuffer ensureCapacityForWrite:bytesToRead];
buffer = [preBuffer writeBuffer];
}
else
{
[currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead];
buffer = (uint8_t *)[currentRead->buffer mutableBytes]
+ currentRead->startOffset
+ currentRead->bytesDone;
}
// Read data into buffer
CFIndex result = CFReadStreamRead(readStream, buffer, (CFIndex)bytesToRead);
LogVerbose(@"CFReadStreamRead(): result = %i", (int)result);
if (result < 0)
{
error = (__bridge_transfer NSError *)CFReadStreamCopyError(readStream);
}
else if (result == 0)
{
socketEOF = YES;
}
else
{
waiting = YES;
bytesRead = (size_t)result;
}
// We only know how many decrypted bytes were read.
// The actual number of bytes read was likely more due to the overhead of the encryption.
// So we reset our flag, and rely on the next callback to alert us of more data.
flags &= ~kSecureSocketHasBytesAvailable;
#endif
}
else
{
// Using SecureTransport for TLS
//
// We know:
// - how many bytes are available on the socket
// - how many encrypted bytes are sitting in the sslPreBuffer
// - how many decypted bytes are sitting in the sslContext
//
// But we do NOT know:
// - how many encypted bytes are sitting in the sslContext
//
// So we play the regular game of using an upper bound instead.
NSUInteger defaultReadLength = (1024 * 32);
if (defaultReadLength < estimatedBytesAvailable) {
defaultReadLength = estimatedBytesAvailable + (1024 * 16);
}
NSUInteger bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength
shouldPreBuffer:&readIntoPreBuffer];
if (bytesToRead > SIZE_MAX) { // NSUInteger may be bigger than size_t
bytesToRead = SIZE_MAX;
}
// Make sure we have enough room in the buffer for our read.
//
// We are either reading directly into the currentRead->buffer,
// or we're reading into the temporary preBuffer.
if (readIntoPreBuffer)
{
[preBuffer ensureCapacityForWrite:bytesToRead];
buffer = [preBuffer writeBuffer];
}
else
{
[currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead];
buffer = (uint8_t *)[currentRead->buffer mutableBytes]
+ currentRead->startOffset
+ currentRead->bytesDone;
}
// The documentation from Apple states:
//
// "a read operation might return errSSLWouldBlock,
// indicating that less data than requested was actually transferred"
//
// However, starting around 10.7, the function will sometimes return noErr,
// even if it didn't read as much data as requested. So we need to watch out for that.
OSStatus result;
do
{
void *loop_buffer = buffer + bytesRead;
size_t loop_bytesToRead = (size_t)bytesToRead - bytesRead;
size_t loop_bytesRead = 0;
result = SSLRead(sslContext, loop_buffer, loop_bytesToRead, &loop_bytesRead);
LogVerbose(@"read from secure socket = %u", (unsigned)loop_bytesRead);
bytesRead += loop_bytesRead;
} while ((result == noErr) && (bytesRead < bytesToRead));
if (result != noErr)
{
if (result == errSSLWouldBlock)
waiting = YES;
else
{
if (result == errSSLClosedGraceful || result == errSSLClosedAbort)
{
// We've reached the end of the stream.
// Handle this the same way we would an EOF from the socket.
socketEOF = YES;
sslErrCode = result;
}
else
{
error = [self sslError:result];
}
}
// It's possible that bytesRead > 0, even if the result was errSSLWouldBlock.
// This happens when the SSLRead function is able to read some data,
// but not the entire amount we requested.
if (bytesRead <= 0)
{
bytesRead = 0;
}
}
// Do not modify socketFDBytesAvailable.
// It will be updated via the SSLReadFunction().
}
}
else
{
// Normal socket operation
NSUInteger bytesToRead;
// There are 3 types of read packets:
//
// 1) Read all available data.
// 2) Read a specific length of data.
// 3) Read up to a particular terminator.
if (currentRead->term != nil)
{
// Read type #3 - read up to a terminator
bytesToRead = [currentRead readLengthForTermWithHint:estimatedBytesAvailable
shouldPreBuffer:&readIntoPreBuffer];
}
else
{
// Read type #1 or #2
bytesToRead = [currentRead readLengthForNonTermWithHint:estimatedBytesAvailable];
}
if (bytesToRead > SIZE_MAX) { // NSUInteger may be bigger than size_t (read param 3)
bytesToRead = SIZE_MAX;
}
// Make sure we have enough room in the buffer for our read.
//
// We are either reading directly into the currentRead->buffer,
// or we're reading into the temporary preBuffer.
if (readIntoPreBuffer)
{
[preBuffer ensureCapacityForWrite:bytesToRead];
buffer = [preBuffer writeBuffer];
}
else
{
[currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead];
buffer = (uint8_t *)[currentRead->buffer mutableBytes]
+ currentRead->startOffset
+ currentRead->bytesDone;
}
// Read data into buffer
int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
ssize_t result = read(socketFD, buffer, (size_t)bytesToRead);
LogVerbose(@"read from socket = %i", (int)result);
if (result < 0)
{
if (errno == EWOULDBLOCK)
waiting = YES;
else
error = [self errorWithErrno:errno reason:@"Error in read() function"];
socketFDBytesAvailable = 0;
}
else if (result == 0)
{
socketEOF = YES;
socketFDBytesAvailable = 0;
}
else
{
bytesRead = result;
if (bytesRead < bytesToRead)
{
// The read returned less data than requested.
// This means socketFDBytesAvailable was a bit off due to timing,
// because we read from the socket right when the readSource event was firing.
socketFDBytesAvailable = 0;
}
else
{
if (socketFDBytesAvailable <= bytesRead)
socketFDBytesAvailable = 0;
else
socketFDBytesAvailable -= bytesRead;
}
if (socketFDBytesAvailable == 0)
{
waiting = YES;
}
}
}
if (bytesRead > 0)
{
// Check to see if the read operation is done
if (currentRead->readLength > 0)
{
// Read type #2 - read a specific length of data
//
// Note: We should never be using a prebuffer when we're reading a specific length of data.
NSAssert(readIntoPreBuffer == NO, @"Invalid logic");
currentRead->bytesDone += bytesRead;
totalBytesReadForCurrentRead += bytesRead;
done = (currentRead->bytesDone == currentRead->readLength);
}
else if (currentRead->term != nil)
{
// Read type #3 - read up to a terminator
if (readIntoPreBuffer)
{
// We just read a big chunk of data into the preBuffer
[preBuffer didWrite:bytesRead];
LogVerbose(@"read data into preBuffer - preBuffer.length = %zu", [preBuffer availableBytes]);
// Search for the terminating sequence
NSUInteger bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done];
LogVerbose(@"copying %lu bytes from preBuffer", (unsigned long)bytesToCopy);
// Ensure there's room on the read packet's buffer
[currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy];
// Copy bytes from prebuffer into read buffer
uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset
+ currentRead->bytesDone;
memcpy(readBuf, [preBuffer readBuffer], bytesToCopy);
// Remove the copied bytes from the prebuffer
[preBuffer didRead:bytesToCopy];
LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]);
// Update totals
currentRead->bytesDone += bytesToCopy;
totalBytesReadForCurrentRead += bytesToCopy;
// Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method above
}
else
{
// We just read a big chunk of data directly into the packet's buffer.
// We need to move any overflow into the prebuffer.
NSInteger overflow = [currentRead searchForTermAfterPreBuffering:bytesRead];
if (overflow == 0)
{
// Perfect match!
// Every byte we read stays in the read buffer,
// and the last byte we read was the last byte of the term.
currentRead->bytesDone += bytesRead;
totalBytesReadForCurrentRead += bytesRead;
done = YES;
}
else if (overflow > 0)
{
// The term was found within the data that we read,
// and there are extra bytes that extend past the end of the term.
// We need to move these excess bytes out of the read packet and into the prebuffer.
NSInteger underflow = bytesRead - overflow;
// Copy excess data into preBuffer
LogVerbose(@"copying %ld overflow bytes into preBuffer", (long)overflow);
[preBuffer ensureCapacityForWrite:overflow];
uint8_t *overflowBuffer = buffer + underflow;
memcpy([preBuffer writeBuffer], overflowBuffer, overflow);
[preBuffer didWrite:overflow];
LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]);
// Note: The completeCurrentRead method will trim the buffer for us.
currentRead->bytesDone += underflow;
totalBytesReadForCurrentRead += underflow;
done = YES;
}
else
{
// The term was not found within the data that we read.
currentRead->bytesDone += bytesRead;
totalBytesReadForCurrentRead += bytesRead;
done = NO;
}
}
if (!done && currentRead->maxLength > 0)
{
// We're not done and there's a set maxLength.
// Have we reached that maxLength yet?
if (currentRead->bytesDone >= currentRead->maxLength)
{
error = [self readMaxedOutError];
}
}
}
else
{
// Read type #1 - read all available data
if (readIntoPreBuffer)
{
// We just read a chunk of data into the preBuffer
[preBuffer didWrite:bytesRead];
// Now copy the data into the read packet.
//
// Recall that we didn't read directly into the packet's buffer to avoid
// over-allocating memory since we had no clue how much data was available to be read.
//
// Ensure there's room on the read packet's buffer
[currentRead ensureCapacityForAdditionalDataOfLength:bytesRead];
// Copy bytes from prebuffer into read buffer
uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset
+ currentRead->bytesDone;
memcpy(readBuf, [preBuffer readBuffer], bytesRead);
// Remove the copied bytes from the prebuffer
[preBuffer didRead:bytesRead];
// Update totals
currentRead->bytesDone += bytesRead;
totalBytesReadForCurrentRead += bytesRead;
}
else
{
currentRead->bytesDone += bytesRead;
totalBytesReadForCurrentRead += bytesRead;
}
done = YES;
}
} // if (bytesRead > 0)
} // if (!done && !error && !socketEOF && hasBytesAvailable)
if (!done && currentRead->readLength == 0 && currentRead->term == nil)
{
// Read type #1 - read all available data
//
// We might arrive here if we read data from the prebuffer but not from the socket.
done = (totalBytesReadForCurrentRead > 0);
}
// Check to see if we're done, or if we've made progress
if (done)
{
[self completeCurrentRead];
if (!error && (!socketEOF || [preBuffer availableBytes] > 0))
{
[self maybeDequeueRead];
}
}
else if (totalBytesReadForCurrentRead > 0)
{
// We're not done read type #2 or #3 yet, but we have read in some bytes
//
// We ensure that `waiting` is set in order to resume the readSource (if it is suspended). It is
// possible to reach this point and `waiting` not be set, if the current read's length is
// sufficiently large. In that case, we may have read to some upperbound successfully, but
// that upperbound could be smaller than the desired length.
waiting = YES;
__strong id theDelegate = delegate;
if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadPartialDataOfLength:tag:)])
{
long theReadTag = currentRead->tag;
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate socket:self didReadPartialDataOfLength:totalBytesReadForCurrentRead tag:theReadTag];
}});
}
}
// Check for errors
if (error)
{
[self closeWithError:error];
}
else if (socketEOF)
{
[self doReadEOF];
}
else if (waiting)
{
if (![self usingCFStreamForTLS])
{
// Monitor the socket for readability (if we're not already doing so)
[self resumeReadSource];
}
}
// Do not add any code here without first adding return statements in the error cases above.
}
- (void)doReadEOF
{
LogTrace();
// This method may be called more than once.
// If the EOF is read while there is still data in the preBuffer,
// then this method may be called continually after invocations of doReadData to see if it's time to disconnect.
flags |= kSocketHasReadEOF;
if (flags & kSocketSecure)
{
// If the SSL layer has any buffered data, flush it into the preBuffer now.
[self flushSSLBuffers];
}
BOOL shouldDisconnect = NO;
NSError *error = nil;
if ((flags & kStartingReadTLS) || (flags & kStartingWriteTLS))
{
// We received an EOF during or prior to startTLS.
// The SSL/TLS handshake is now impossible, so this is an unrecoverable situation.
shouldDisconnect = YES;
if ([self usingSecureTransportForTLS])
{
error = [self sslError:errSSLClosedAbort];
}
}
else if (flags & kReadStreamClosed)
{
// The preBuffer has already been drained.
// The config allows half-duplex connections.
// We've previously checked the socket, and it appeared writeable.
// So we marked the read stream as closed and notified the delegate.
//
// As per the half-duplex contract, the socket will be closed when a write fails,
// or when the socket is manually closed.
shouldDisconnect = NO;
}
else if ([preBuffer availableBytes] > 0)
{
LogVerbose(@"Socket reached EOF, but there is still data available in prebuffer");
// Although we won't be able to read any more data from the socket,
// there is existing data that has been prebuffered that we can read.
shouldDisconnect = NO;
}
else if (config & kAllowHalfDuplexConnection)
{
// We just received an EOF (end of file) from the socket's read stream.
// This means the remote end of the socket (the peer we're connected to)
// has explicitly stated that it will not be sending us any more data.
//
// Query the socket to see if it is still writeable. (Perhaps the peer will continue reading data from us)
int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
struct pollfd pfd[1];
pfd[0].fd = socketFD;
pfd[0].events = POLLOUT;
pfd[0].revents = 0;
poll(pfd, 1, 0);
if (pfd[0].revents & POLLOUT)
{
// Socket appears to still be writeable
shouldDisconnect = NO;
flags |= kReadStreamClosed;
// Notify the delegate that we're going half-duplex
__strong id theDelegate = delegate;
if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidCloseReadStream:)])
{
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate socketDidCloseReadStream:self];
}});
}
}
else
{
shouldDisconnect = YES;
}
}
else
{
shouldDisconnect = YES;
}
if (shouldDisconnect)
{
if (error == nil)
{
if ([self usingSecureTransportForTLS])
{
if (sslErrCode != noErr && sslErrCode != errSSLClosedGraceful)
{
error = [self sslError:sslErrCode];
}
else
{
error = [self connectionClosedError];
}
}
else
{
error = [self connectionClosedError];
}
}
[self closeWithError:error];
}
else
{
if (![self usingCFStreamForTLS])
{
// Suspend the read source (if needed)
[self suspendReadSource];
}
}
}
- (void)completeCurrentRead
{
LogTrace();
NSAssert(currentRead, @"Trying to complete current read when there is no current read.");
NSData *result = nil;
if (currentRead->bufferOwner)
{
// We created the buffer on behalf of the user.
// Trim our buffer to be the proper size.
[currentRead->buffer setLength:currentRead->bytesDone];
result = currentRead->buffer;
}
else
{
// We did NOT create the buffer.
// The buffer is owned by the caller.
// Only trim the buffer if we had to increase its size.
if ([currentRead->buffer length] > currentRead->originalBufferLength)
{
NSUInteger readSize = currentRead->startOffset + currentRead->bytesDone;
NSUInteger origSize = currentRead->originalBufferLength;
NSUInteger buffSize = MAX(readSize, origSize);
[currentRead->buffer setLength:buffSize];
}
uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset;
result = [NSData dataWithBytesNoCopy:buffer length:currentRead->bytesDone freeWhenDone:NO];
}
__strong id theDelegate = delegate;
if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadData:withTag:)])
{
GCDAsyncReadPacket *theRead = currentRead; // Ensure currentRead retained since result may not own buffer
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate socket:self didReadData:result withTag:theRead->tag];
}});
}
[self endCurrentRead];
}
- (void)endCurrentRead
{
if (readTimer)
{
dispatch_source_cancel(readTimer);
readTimer = NULL;
}
currentRead = nil;
}
- (void)setupReadTimerWithTimeout:(NSTimeInterval)timeout
{
if (timeout >= 0.0)
{
readTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
__weak GCDAsyncSocket *weakSelf = self;
dispatch_source_set_event_handler(readTimer, ^{ @autoreleasepool {
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
__strong GCDAsyncSocket *strongSelf = weakSelf;
if (strongSelf == nil) return_from_block;
[strongSelf doReadTimeout];
#pragma clang diagnostic pop
}});
#if !OS_OBJECT_USE_OBJC
dispatch_source_t theReadTimer = readTimer;
dispatch_source_set_cancel_handler(readTimer, ^{
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
LogVerbose(@"dispatch_release(readTimer)");
dispatch_release(theReadTimer);
#pragma clang diagnostic pop
});
#endif
dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC));
dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0);
dispatch_resume(readTimer);
}
}
- (void)doReadTimeout
{
// This is a little bit tricky.
// Ideally we'd like to synchronously query the delegate about a timeout extension.
// But if we do so synchronously we risk a possible deadlock.
// So instead we have to do so asynchronously, and callback to ourselves from within the delegate block.
flags |= kReadsPaused;
__strong id theDelegate = delegate;
if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:shouldTimeoutReadWithTag:elapsed:bytesDone:)])
{
GCDAsyncReadPacket *theRead = currentRead;
dispatch_async(delegateQueue, ^{ @autoreleasepool {
NSTimeInterval timeoutExtension = 0.0;
timeoutExtension = [theDelegate socket:self shouldTimeoutReadWithTag:theRead->tag
elapsed:theRead->timeout
bytesDone:theRead->bytesDone];
dispatch_async(self->socketQueue, ^{ @autoreleasepool {
[self doReadTimeoutWithExtension:timeoutExtension];
}});
}});
}
else
{
[self doReadTimeoutWithExtension:0.0];
}
}
- (void)doReadTimeoutWithExtension:(NSTimeInterval)timeoutExtension
{
if (currentRead)
{
if (timeoutExtension > 0.0)
{
currentRead->timeout += timeoutExtension;
// Reschedule the timer
dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeoutExtension * NSEC_PER_SEC));
dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0);
// Unpause reads, and continue
flags &= ~kReadsPaused;
[self doReadData];
}
else
{
LogVerbose(@"ReadTimeout");
[self closeWithError:[self readTimeoutError]];
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Writing
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag
{
if ([data length] == 0) return;
GCDAsyncWritePacket *packet = [[GCDAsyncWritePacket alloc] initWithData:data timeout:timeout tag:tag];
dispatch_async(socketQueue, ^{ @autoreleasepool {
LogTrace();
if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites))
{
[self->writeQueue addObject:packet];
[self maybeDequeueWrite];
}
}});
// Do not rely on the block being run in order to release the packet,
// as the queue might get released without the block completing.
}
- (float)progressOfWriteReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr
{
__block float result = 0.0F;
dispatch_block_t block = ^{
if (!self->currentWrite || ![self->currentWrite isKindOfClass:[GCDAsyncWritePacket class]])
{
// We're not writing anything right now.
if (tagPtr != NULL) *tagPtr = 0;
if (donePtr != NULL) *donePtr = 0;
if (totalPtr != NULL) *totalPtr = 0;
result = NAN;
}
else
{
NSUInteger done = self->currentWrite->bytesDone;
NSUInteger total = [self->currentWrite->buffer length];
if (tagPtr != NULL) *tagPtr = self->currentWrite->tag;
if (donePtr != NULL) *donePtr = done;
if (totalPtr != NULL) *totalPtr = total;
result = (float)done / (float)total;
}
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
/**
* Conditionally starts a new write.
*
* It is called when:
* - a user requests a write
* - after a write request has finished (to handle the next request)
* - immediately after the socket opens to handle any pending requests
*
* This method also handles auto-disconnect post read/write completion.
**/
- (void)maybeDequeueWrite
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
// If we're not currently processing a write AND we have an available write stream
if ((currentWrite == nil) && (flags & kConnected))
{
if ([writeQueue count] > 0)
{
// Dequeue the next object in the write queue
currentWrite = [writeQueue objectAtIndex:0];
[writeQueue removeObjectAtIndex:0];
if ([currentWrite isKindOfClass:[GCDAsyncSpecialPacket class]])
{
LogVerbose(@"Dequeued GCDAsyncSpecialPacket");
// Attempt to start TLS
flags |= kStartingWriteTLS;
// This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set
[self maybeStartTLS];
}
else
{
LogVerbose(@"Dequeued GCDAsyncWritePacket");
// Setup write timer (if needed)
[self setupWriteTimerWithTimeout:currentWrite->timeout];
// Immediately write, if possible
[self doWriteData];
}
}
else if (flags & kDisconnectAfterWrites)
{
if (flags & kDisconnectAfterReads)
{
if (([readQueue count] == 0) && (currentRead == nil))
{
[self closeWithError:nil];
}
}
else
{
[self closeWithError:nil];
}
}
}
}
- (void)doWriteData
{
LogTrace();
// This method is called by the writeSource via the socketQueue
if ((currentWrite == nil) || (flags & kWritesPaused))
{
LogVerbose(@"No currentWrite or kWritesPaused");
// Unable to write at this time
if ([self usingCFStreamForTLS])
{
// CFWriteStream only fires once when there is available data.
// It won't fire again until we've invoked CFWriteStreamWrite.
}
else
{
// If the writeSource is firing, we need to pause it
// or else it will continue to fire over and over again.
if (flags & kSocketCanAcceptBytes)
{
[self suspendWriteSource];
}
}
return;
}
if (!(flags & kSocketCanAcceptBytes))
{
LogVerbose(@"No space available to write...");
// No space available to write.
if (![self usingCFStreamForTLS])
{
// Need to wait for writeSource to fire and notify us of
// available space in the socket's internal write buffer.
[self resumeWriteSource];
}
return;
}
if (flags & kStartingWriteTLS)
{
LogVerbose(@"Waiting for SSL/TLS handshake to complete");
// The writeQueue is waiting for SSL/TLS handshake to complete.
if (flags & kStartingReadTLS)
{
if ([self usingSecureTransportForTLS] && lastSSLHandshakeError == errSSLWouldBlock)
{
// We are in the process of a SSL Handshake.
// We were waiting for available space in the socket's internal OS buffer to continue writing.
[self ssl_continueSSLHandshake];
}
}
else
{
// We are still waiting for the readQueue to drain and start the SSL/TLS process.
// We now know we can write to the socket.
if (![self usingCFStreamForTLS])
{
// Suspend the write source or else it will continue to fire nonstop.
[self suspendWriteSource];
}
}
return;
}
// Note: This method is not called if currentWrite is a GCDAsyncSpecialPacket (startTLS packet)
BOOL waiting = NO;
NSError *error = nil;
size_t bytesWritten = 0;
if (flags & kSocketSecure)
{
if ([self usingCFStreamForTLS])
{
#if TARGET_OS_IPHONE
//
// Writing data using CFStream (over internal TLS)
//
const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone;
NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone;
if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3)
{
bytesToWrite = SIZE_MAX;
}
CFIndex result = CFWriteStreamWrite(writeStream, buffer, (CFIndex)bytesToWrite);
LogVerbose(@"CFWriteStreamWrite(%lu) = %li", (unsigned long)bytesToWrite, result);
if (result < 0)
{
error = (__bridge_transfer NSError *)CFWriteStreamCopyError(writeStream);
}
else
{
bytesWritten = (size_t)result;
// We always set waiting to true in this scenario.
// CFStream may have altered our underlying socket to non-blocking.
// Thus if we attempt to write without a callback, we may end up blocking our queue.
waiting = YES;
}
#endif
}
else
{
// We're going to use the SSLWrite function.
//
// OSStatus SSLWrite(SSLContextRef context, const void *data, size_t dataLength, size_t *processed)
//
// Parameters:
// context - An SSL session context reference.
// data - A pointer to the buffer of data to write.
// dataLength - The amount, in bytes, of data to write.
// processed - On return, the length, in bytes, of the data actually written.
//
// It sounds pretty straight-forward,
// but there are a few caveats you should be aware of.
//
// The SSLWrite method operates in a non-obvious (and rather annoying) manner.
// According to the documentation:
//
// Because you may configure the underlying connection to operate in a non-blocking manner,
// a write operation might return errSSLWouldBlock, indicating that less data than requested
// was actually transferred. In this case, you should repeat the call to SSLWrite until some
// other result is returned.
//
// This sounds perfect, but when our SSLWriteFunction returns errSSLWouldBlock,
// then the SSLWrite method returns (with the proper errSSLWouldBlock return value),
// but it sets processed to dataLength !!
//
// In other words, if the SSLWrite function doesn't completely write all the data we tell it to,
// then it doesn't tell us how many bytes were actually written. So, for example, if we tell it to
// write 256 bytes then it might actually write 128 bytes, but then report 0 bytes written.
//
// You might be wondering:
// If the SSLWrite function doesn't tell us how many bytes were written,
// then how in the world are we supposed to update our parameters (buffer & bytesToWrite)
// for the next time we invoke SSLWrite?
//
// The answer is that SSLWrite cached all the data we told it to write,
// and it will push out that data next time we call SSLWrite.
// If we call SSLWrite with new data, it will push out the cached data first, and then the new data.
// If we call SSLWrite with empty data, then it will simply push out the cached data.
//
// For this purpose we're going to break large writes into a series of smaller writes.
// This allows us to report progress back to the delegate.
OSStatus result;
BOOL hasCachedDataToWrite = (sslWriteCachedLength > 0);
BOOL hasNewDataToWrite = YES;
if (hasCachedDataToWrite)
{
size_t processed = 0;
result = SSLWrite(sslContext, NULL, 0, &processed);
if (result == noErr)
{
bytesWritten = sslWriteCachedLength;
sslWriteCachedLength = 0;
if ([currentWrite->buffer length] == (currentWrite->bytesDone + bytesWritten))
{
// We've written all data for the current write.
hasNewDataToWrite = NO;
}
}
else
{
if (result == errSSLWouldBlock)
{
waiting = YES;
}
else
{
error = [self sslError:result];
}
// Can't write any new data since we were unable to write the cached data.
hasNewDataToWrite = NO;
}
}
if (hasNewDataToWrite)
{
const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes]
+ currentWrite->bytesDone
+ bytesWritten;
NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone - bytesWritten;
if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3)
{
bytesToWrite = SIZE_MAX;
}
size_t bytesRemaining = bytesToWrite;
BOOL keepLooping = YES;
while (keepLooping)
{
const size_t sslMaxBytesToWrite = 32768;
size_t sslBytesToWrite = MIN(bytesRemaining, sslMaxBytesToWrite);
size_t sslBytesWritten = 0;
result = SSLWrite(sslContext, buffer, sslBytesToWrite, &sslBytesWritten);
if (result == noErr)
{
buffer += sslBytesWritten;
bytesWritten += sslBytesWritten;
bytesRemaining -= sslBytesWritten;
keepLooping = (bytesRemaining > 0);
}
else
{
if (result == errSSLWouldBlock)
{
waiting = YES;
sslWriteCachedLength = sslBytesToWrite;
}
else
{
error = [self sslError:result];
}
keepLooping = NO;
}
} // while (keepLooping)
} // if (hasNewDataToWrite)
}
}
else
{
//
// Writing data directly over raw socket
//
int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone;
NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone;
if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3)
{
bytesToWrite = SIZE_MAX;
}
ssize_t result = write(socketFD, buffer, (size_t)bytesToWrite);
LogVerbose(@"wrote to socket = %zd", result);
// Check results
if (result < 0)
{
if (errno == EWOULDBLOCK)
{
waiting = YES;
}
else
{
error = [self errorWithErrno:errno reason:@"Error in write() function"];
}
}
else
{
bytesWritten = result;
}
}
// We're done with our writing.
// If we explictly ran into a situation where the socket told us there was no room in the buffer,
// then we immediately resume listening for notifications.
//
// We must do this before we dequeue another write,
// as that may in turn invoke this method again.
//
// Note that if CFStream is involved, it may have maliciously put our socket in blocking mode.
if (waiting)
{
flags &= ~kSocketCanAcceptBytes;
if (![self usingCFStreamForTLS])
{
[self resumeWriteSource];
}
}
// Check our results
BOOL done = NO;
if (bytesWritten > 0)
{
// Update total amount read for the current write
currentWrite->bytesDone += bytesWritten;
LogVerbose(@"currentWrite->bytesDone = %lu", (unsigned long)currentWrite->bytesDone);
// Is packet done?
done = (currentWrite->bytesDone == [currentWrite->buffer length]);
}
if (done)
{
[self completeCurrentWrite];
if (!error)
{
dispatch_async(socketQueue, ^{ @autoreleasepool{
[self maybeDequeueWrite];
}});
}
}
else
{
// We were unable to finish writing the data,
// so we're waiting for another callback to notify us of available space in the lower-level output buffer.
if (!waiting && !error)
{
// This would be the case if our write was able to accept some data, but not all of it.
flags &= ~kSocketCanAcceptBytes;
if (![self usingCFStreamForTLS])
{
[self resumeWriteSource];
}
}
if (bytesWritten > 0)
{
// We're not done with the entire write, but we have written some bytes
__strong id theDelegate = delegate;
if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didWritePartialDataOfLength:tag:)])
{
long theWriteTag = currentWrite->tag;
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate socket:self didWritePartialDataOfLength:bytesWritten tag:theWriteTag];
}});
}
}
}
// Check for errors
if (error)
{
[self closeWithError:[self errorWithErrno:errno reason:@"Error in write() function"]];
}
// Do not add any code here without first adding a return statement in the error case above.
}
- (void)completeCurrentWrite
{
LogTrace();
NSAssert(currentWrite, @"Trying to complete current write when there is no current write.");
__strong id theDelegate = delegate;
if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didWriteDataWithTag:)])
{
long theWriteTag = currentWrite->tag;
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate socket:self didWriteDataWithTag:theWriteTag];
}});
}
[self endCurrentWrite];
}
- (void)endCurrentWrite
{
if (writeTimer)
{
dispatch_source_cancel(writeTimer);
writeTimer = NULL;
}
currentWrite = nil;
}
- (void)setupWriteTimerWithTimeout:(NSTimeInterval)timeout
{
if (timeout >= 0.0)
{
writeTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
__weak GCDAsyncSocket *weakSelf = self;
dispatch_source_set_event_handler(writeTimer, ^{ @autoreleasepool {
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
__strong GCDAsyncSocket *strongSelf = weakSelf;
if (strongSelf == nil) return_from_block;
[strongSelf doWriteTimeout];
#pragma clang diagnostic pop
}});
#if !OS_OBJECT_USE_OBJC
dispatch_source_t theWriteTimer = writeTimer;
dispatch_source_set_cancel_handler(writeTimer, ^{
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
LogVerbose(@"dispatch_release(writeTimer)");
dispatch_release(theWriteTimer);
#pragma clang diagnostic pop
});
#endif
dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC));
dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0);
dispatch_resume(writeTimer);
}
}
- (void)doWriteTimeout
{
// This is a little bit tricky.
// Ideally we'd like to synchronously query the delegate about a timeout extension.
// But if we do so synchronously we risk a possible deadlock.
// So instead we have to do so asynchronously, and callback to ourselves from within the delegate block.
flags |= kWritesPaused;
__strong id theDelegate = delegate;
if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:shouldTimeoutWriteWithTag:elapsed:bytesDone:)])
{
GCDAsyncWritePacket *theWrite = currentWrite;
dispatch_async(delegateQueue, ^{ @autoreleasepool {
NSTimeInterval timeoutExtension = 0.0;
timeoutExtension = [theDelegate socket:self shouldTimeoutWriteWithTag:theWrite->tag
elapsed:theWrite->timeout
bytesDone:theWrite->bytesDone];
dispatch_async(self->socketQueue, ^{ @autoreleasepool {
[self doWriteTimeoutWithExtension:timeoutExtension];
}});
}});
}
else
{
[self doWriteTimeoutWithExtension:0.0];
}
}
- (void)doWriteTimeoutWithExtension:(NSTimeInterval)timeoutExtension
{
if (currentWrite)
{
if (timeoutExtension > 0.0)
{
currentWrite->timeout += timeoutExtension;
// Reschedule the timer
dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeoutExtension * NSEC_PER_SEC));
dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0);
// Unpause writes, and continue
flags &= ~kWritesPaused;
[self doWriteData];
}
else
{
LogVerbose(@"WriteTimeout");
[self closeWithError:[self writeTimeoutError]];
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Security
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)startTLS:(NSDictionary *)tlsSettings
{
LogTrace();
if (tlsSettings == nil)
{
// Passing nil/NULL to CFReadStreamSetProperty will appear to work the same as passing an empty dictionary,
// but causes problems if we later try to fetch the remote host's certificate.
//
// To be exact, it causes the following to return NULL instead of the normal result:
// CFReadStreamCopyProperty(readStream, kCFStreamPropertySSLPeerCertificates)
//
// So we use an empty dictionary instead, which works perfectly.
tlsSettings = [NSDictionary dictionary];
}
GCDAsyncSpecialPacket *packet = [[GCDAsyncSpecialPacket alloc] initWithTLSSettings:tlsSettings];
dispatch_async(socketQueue, ^{ @autoreleasepool {
if ((self->flags & kSocketStarted) && !(self->flags & kQueuedTLS) && !(self->flags & kForbidReadsWrites))
{
[self->readQueue addObject:packet];
[self->writeQueue addObject:packet];
self->flags |= kQueuedTLS;
[self maybeDequeueRead];
[self maybeDequeueWrite];
}
}});
}
- (void)maybeStartTLS
{
// We can't start TLS until:
// - All queued reads prior to the user calling startTLS are complete
// - All queued writes prior to the user calling startTLS are complete
//
// We'll know these conditions are met when both kStartingReadTLS and kStartingWriteTLS are set
if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS))
{
BOOL useSecureTransport = YES;
#if TARGET_OS_IPHONE
{
GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead;
NSDictionary *tlsSettings = @{};
if (tlsPacket) {
tlsSettings = tlsPacket->tlsSettings;
}
NSNumber *value = [tlsSettings objectForKey:GCDAsyncSocketUseCFStreamForTLS];
if (value && [value boolValue])
useSecureTransport = NO;
}
#endif
if (useSecureTransport)
{
[self ssl_startTLS];
}
else
{
#if TARGET_OS_IPHONE
[self cf_startTLS];
#endif
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Security via SecureTransport
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (OSStatus)sslReadWithBuffer:(void *)buffer length:(size_t *)bufferLength
{
LogVerbose(@"sslReadWithBuffer:%p length:%lu", buffer, (unsigned long)*bufferLength);
if ((socketFDBytesAvailable == 0) && ([sslPreBuffer availableBytes] == 0))
{
LogVerbose(@"%@ - No data available to read...", THIS_METHOD);
// No data available to read.
//
// Need to wait for readSource to fire and notify us of
// available data in the socket's internal read buffer.
[self resumeReadSource];
*bufferLength = 0;
return errSSLWouldBlock;
}
size_t totalBytesRead = 0;
size_t totalBytesLeftToBeRead = *bufferLength;
BOOL done = NO;
BOOL socketError = NO;
//
// STEP 1 : READ FROM SSL PRE BUFFER
//
size_t sslPreBufferLength = [sslPreBuffer availableBytes];
if (sslPreBufferLength > 0)
{
LogVerbose(@"%@: Reading from SSL pre buffer...", THIS_METHOD);
size_t bytesToCopy;
if (sslPreBufferLength > totalBytesLeftToBeRead)
bytesToCopy = totalBytesLeftToBeRead;
else
bytesToCopy = sslPreBufferLength;
LogVerbose(@"%@: Copying %zu bytes from sslPreBuffer", THIS_METHOD, bytesToCopy);
memcpy(buffer, [sslPreBuffer readBuffer], bytesToCopy);
[sslPreBuffer didRead:bytesToCopy];
LogVerbose(@"%@: sslPreBuffer.length = %zu", THIS_METHOD, [sslPreBuffer availableBytes]);
totalBytesRead += bytesToCopy;
totalBytesLeftToBeRead -= bytesToCopy;
done = (totalBytesLeftToBeRead == 0);
if (done) LogVerbose(@"%@: Complete", THIS_METHOD);
}
//
// STEP 2 : READ FROM SOCKET
//
if (!done && (socketFDBytesAvailable > 0))
{
LogVerbose(@"%@: Reading from socket...", THIS_METHOD);
int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
BOOL readIntoPreBuffer;
size_t bytesToRead;
uint8_t *buf;
if (socketFDBytesAvailable > totalBytesLeftToBeRead)
{
// Read all available data from socket into sslPreBuffer.
// Then copy requested amount into dataBuffer.
LogVerbose(@"%@: Reading into sslPreBuffer...", THIS_METHOD);
[sslPreBuffer ensureCapacityForWrite:socketFDBytesAvailable];
readIntoPreBuffer = YES;
bytesToRead = (size_t)socketFDBytesAvailable;
buf = [sslPreBuffer writeBuffer];
}
else
{
// Read available data from socket directly into dataBuffer.
LogVerbose(@"%@: Reading directly into dataBuffer...", THIS_METHOD);
readIntoPreBuffer = NO;
bytesToRead = totalBytesLeftToBeRead;
buf = (uint8_t *)buffer + totalBytesRead;
}
ssize_t result = read(socketFD, buf, bytesToRead);
LogVerbose(@"%@: read from socket = %zd", THIS_METHOD, result);
if (result < 0)
{
LogVerbose(@"%@: read errno = %i", THIS_METHOD, errno);
if (errno != EWOULDBLOCK)
{
socketError = YES;
}
socketFDBytesAvailable = 0;
}
else if (result == 0)
{
LogVerbose(@"%@: read EOF", THIS_METHOD);
socketError = YES;
socketFDBytesAvailable = 0;
}
else
{
size_t bytesReadFromSocket = result;
if (socketFDBytesAvailable > bytesReadFromSocket)
socketFDBytesAvailable -= bytesReadFromSocket;
else
socketFDBytesAvailable = 0;
if (readIntoPreBuffer)
{
[sslPreBuffer didWrite:bytesReadFromSocket];
size_t bytesToCopy = MIN(totalBytesLeftToBeRead, bytesReadFromSocket);
LogVerbose(@"%@: Copying %zu bytes out of sslPreBuffer", THIS_METHOD, bytesToCopy);
memcpy((uint8_t *)buffer + totalBytesRead, [sslPreBuffer readBuffer], bytesToCopy);
[sslPreBuffer didRead:bytesToCopy];
totalBytesRead += bytesToCopy;
totalBytesLeftToBeRead -= bytesToCopy;
LogVerbose(@"%@: sslPreBuffer.length = %zu", THIS_METHOD, [sslPreBuffer availableBytes]);
}
else
{
totalBytesRead += bytesReadFromSocket;
totalBytesLeftToBeRead -= bytesReadFromSocket;
}
done = (totalBytesLeftToBeRead == 0);
if (done) LogVerbose(@"%@: Complete", THIS_METHOD);
}
}
*bufferLength = totalBytesRead;
if (done)
return noErr;
if (socketError)
return errSSLClosedAbort;
return errSSLWouldBlock;
}
- (OSStatus)sslWriteWithBuffer:(const void *)buffer length:(size_t *)bufferLength
{
if (!(flags & kSocketCanAcceptBytes))
{
// Unable to write.
//
// Need to wait for writeSource to fire and notify us of
// available space in the socket's internal write buffer.
[self resumeWriteSource];
*bufferLength = 0;
return errSSLWouldBlock;
}
size_t bytesToWrite = *bufferLength;
size_t bytesWritten = 0;
BOOL done = NO;
BOOL socketError = NO;
int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
ssize_t result = write(socketFD, buffer, bytesToWrite);
if (result < 0)
{
if (errno != EWOULDBLOCK)
{
socketError = YES;
}
flags &= ~kSocketCanAcceptBytes;
}
else if (result == 0)
{
flags &= ~kSocketCanAcceptBytes;
}
else
{
bytesWritten = result;
done = (bytesWritten == bytesToWrite);
}
*bufferLength = bytesWritten;
if (done)
return noErr;
if (socketError)
return errSSLClosedAbort;
return errSSLWouldBlock;
}
static OSStatus SSLReadFunction(SSLConnectionRef connection, void *data, size_t *dataLength)
{
GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection;
NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey), @"What the deuce?");
return [asyncSocket sslReadWithBuffer:data length:dataLength];
}
static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data, size_t *dataLength)
{
GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection;
NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey), @"What the deuce?");
return [asyncSocket sslWriteWithBuffer:data length:dataLength];
}
- (void)ssl_startTLS
{
LogTrace();
LogVerbose(@"Starting TLS (via SecureTransport)...");
OSStatus status;
GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead;
if (tlsPacket == nil) // Code to quiet the analyzer
{
NSAssert(NO, @"Logic error");
[self closeWithError:[self otherError:@"Logic error"]];
return;
}
NSDictionary *tlsSettings = tlsPacket->tlsSettings;
// Create SSLContext, and setup IO callbacks and connection ref
NSNumber *isServerNumber = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLIsServer];
BOOL isServer = [isServerNumber boolValue];
#if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080)
{
if (isServer)
sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLServerSide, kSSLStreamType);
else
sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType);
if (sslContext == NULL)
{
[self closeWithError:[self otherError:@"Error in SSLCreateContext"]];
return;
}
}
#else // (__MAC_OS_X_VERSION_MIN_REQUIRED < 1080)
{
status = SSLNewContext(isServer, &sslContext);
if (status != noErr)
{
[self closeWithError:[self otherError:@"Error in SSLNewContext"]];
return;
}
}
#endif
status = SSLSetIOFuncs(sslContext, &SSLReadFunction, &SSLWriteFunction);
if (status != noErr)
{
[self closeWithError:[self otherError:@"Error in SSLSetIOFuncs"]];
return;
}
status = SSLSetConnection(sslContext, (__bridge SSLConnectionRef)self);
if (status != noErr)
{
[self closeWithError:[self otherError:@"Error in SSLSetConnection"]];
return;
}
NSNumber *shouldManuallyEvaluateTrust = [tlsSettings objectForKey:GCDAsyncSocketManuallyEvaluateTrust];
if ([shouldManuallyEvaluateTrust boolValue])
{
if (isServer)
{
[self closeWithError:[self otherError:@"Manual trust validation is not supported for server sockets"]];
return;
}
status = SSLSetSessionOption(sslContext, kSSLSessionOptionBreakOnServerAuth, true);
if (status != noErr)
{
[self closeWithError:[self otherError:@"Error in SSLSetSessionOption"]];
return;
}
#if !TARGET_OS_IPHONE && (__MAC_OS_X_VERSION_MIN_REQUIRED < 1080)
// Note from Apple's documentation:
//
// It is only necessary to call SSLSetEnableCertVerify on the Mac prior to OS X 10.8.
// On OS X 10.8 and later setting kSSLSessionOptionBreakOnServerAuth always disables the
// built-in trust evaluation. All versions of iOS behave like OS X 10.8 and thus
// SSLSetEnableCertVerify is not available on that platform at all.
status = SSLSetEnableCertVerify(sslContext, NO);
if (status != noErr)
{
[self closeWithError:[self otherError:@"Error in SSLSetEnableCertVerify"]];
return;
}
#endif
}
// Configure SSLContext from given settings
//
// Checklist:
// 1. kCFStreamSSLPeerName
// 2. kCFStreamSSLCertificates
// 3. GCDAsyncSocketSSLPeerID
// 4. GCDAsyncSocketSSLProtocolVersionMin
// 5. GCDAsyncSocketSSLProtocolVersionMax
// 6. GCDAsyncSocketSSLSessionOptionFalseStart
// 7. GCDAsyncSocketSSLSessionOptionSendOneByteRecord
// 8. GCDAsyncSocketSSLCipherSuites
// 9. GCDAsyncSocketSSLDiffieHellmanParameters (Mac)
// 10. GCDAsyncSocketSSLALPN
//
// Deprecated (throw error):
// 10. kCFStreamSSLAllowsAnyRoot
// 11. kCFStreamSSLAllowsExpiredRoots
// 12. kCFStreamSSLAllowsExpiredCertificates
// 13. kCFStreamSSLValidatesCertificateChain
// 14. kCFStreamSSLLevel
NSObject *value;
// 1. kCFStreamSSLPeerName
value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLPeerName];
if ([value isKindOfClass:[NSString class]])
{
NSString *peerName = (NSString *)value;
const char *peer = [peerName UTF8String];
size_t peerLen = strlen(peer);
status = SSLSetPeerDomainName(sslContext, peer, peerLen);
if (status != noErr)
{
[self closeWithError:[self otherError:@"Error in SSLSetPeerDomainName"]];
return;
}
}
else if (value)
{
NSAssert(NO, @"Invalid value for kCFStreamSSLPeerName. Value must be of type NSString.");
[self closeWithError:[self otherError:@"Invalid value for kCFStreamSSLPeerName."]];
return;
}
// 2. kCFStreamSSLCertificates
value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLCertificates];
if ([value isKindOfClass:[NSArray class]])
{
NSArray *certs = (NSArray *)value;
status = SSLSetCertificate(sslContext, (__bridge CFArrayRef)certs);
if (status != noErr)
{
[self closeWithError:[self otherError:@"Error in SSLSetCertificate"]];
return;
}
}
else if (value)
{
NSAssert(NO, @"Invalid value for kCFStreamSSLCertificates. Value must be of type NSArray.");
[self closeWithError:[self otherError:@"Invalid value for kCFStreamSSLCertificates."]];
return;
}
// 3. GCDAsyncSocketSSLPeerID
value = [tlsSettings objectForKey:GCDAsyncSocketSSLPeerID];
if ([value isKindOfClass:[NSData class]])
{
NSData *peerIdData = (NSData *)value;
status = SSLSetPeerID(sslContext, [peerIdData bytes], [peerIdData length]);
if (status != noErr)
{
[self closeWithError:[self otherError:@"Error in SSLSetPeerID"]];
return;
}
}
else if (value)
{
NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLPeerID. Value must be of type NSData."
@" (You can convert strings to data using a method like"
@" [string dataUsingEncoding:NSUTF8StringEncoding])");
[self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLPeerID."]];
return;
}
// 4. GCDAsyncSocketSSLProtocolVersionMin
value = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMin];
if ([value isKindOfClass:[NSNumber class]])
{
SSLProtocol minProtocol = (SSLProtocol)[(NSNumber *)value intValue];
if (minProtocol != kSSLProtocolUnknown)
{
status = SSLSetProtocolVersionMin(sslContext, minProtocol);
if (status != noErr)
{
[self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMin"]];
return;
}
}
}
else if (value)
{
NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLProtocolVersionMin. Value must be of type NSNumber.");
[self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLProtocolVersionMin."]];
return;
}
// 5. GCDAsyncSocketSSLProtocolVersionMax
value = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMax];
if ([value isKindOfClass:[NSNumber class]])
{
SSLProtocol maxProtocol = (SSLProtocol)[(NSNumber *)value intValue];
if (maxProtocol != kSSLProtocolUnknown)
{
status = SSLSetProtocolVersionMax(sslContext, maxProtocol);
if (status != noErr)
{
[self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMax"]];
return;
}
}
}
else if (value)
{
NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLProtocolVersionMax. Value must be of type NSNumber.");
[self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLProtocolVersionMax."]];
return;
}
// 6. GCDAsyncSocketSSLSessionOptionFalseStart
value = [tlsSettings objectForKey:GCDAsyncSocketSSLSessionOptionFalseStart];
if ([value isKindOfClass:[NSNumber class]])
{
NSNumber *falseStart = (NSNumber *)value;
status = SSLSetSessionOption(sslContext, kSSLSessionOptionFalseStart, [falseStart boolValue]);
if (status != noErr)
{
[self closeWithError:[self otherError:@"Error in SSLSetSessionOption (kSSLSessionOptionFalseStart)"]];
return;
}
}
else if (value)
{
NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLSessionOptionFalseStart. Value must be of type NSNumber.");
[self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLSessionOptionFalseStart."]];
return;
}
// 7. GCDAsyncSocketSSLSessionOptionSendOneByteRecord
value = [tlsSettings objectForKey:GCDAsyncSocketSSLSessionOptionSendOneByteRecord];
if ([value isKindOfClass:[NSNumber class]])
{
NSNumber *oneByteRecord = (NSNumber *)value;
status = SSLSetSessionOption(sslContext, kSSLSessionOptionSendOneByteRecord, [oneByteRecord boolValue]);
if (status != noErr)
{
[self closeWithError:
[self otherError:@"Error in SSLSetSessionOption (kSSLSessionOptionSendOneByteRecord)"]];
return;
}
}
else if (value)
{
NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLSessionOptionSendOneByteRecord."
@" Value must be of type NSNumber.");
[self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLSessionOptionSendOneByteRecord."]];
return;
}
// 8. GCDAsyncSocketSSLCipherSuites
value = [tlsSettings objectForKey:GCDAsyncSocketSSLCipherSuites];
if ([value isKindOfClass:[NSArray class]])
{
NSArray *cipherSuites = (NSArray *)value;
NSUInteger numberCiphers = [cipherSuites count];
SSLCipherSuite ciphers[numberCiphers];
NSUInteger cipherIndex;
for (cipherIndex = 0; cipherIndex < numberCiphers; cipherIndex++)
{
NSNumber *cipherObject = [cipherSuites objectAtIndex:cipherIndex];
ciphers[cipherIndex] = (SSLCipherSuite)[cipherObject unsignedIntValue];
}
status = SSLSetEnabledCiphers(sslContext, ciphers, numberCiphers);
if (status != noErr)
{
[self closeWithError:[self otherError:@"Error in SSLSetEnabledCiphers"]];
return;
}
}
else if (value)
{
NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLCipherSuites. Value must be of type NSArray.");
[self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLCipherSuites."]];
return;
}
// 9. GCDAsyncSocketSSLDiffieHellmanParameters
#if !TARGET_OS_IPHONE
value = [tlsSettings objectForKey:GCDAsyncSocketSSLDiffieHellmanParameters];
if ([value isKindOfClass:[NSData class]])
{
NSData *diffieHellmanData = (NSData *)value;
status = SSLSetDiffieHellmanParams(sslContext, [diffieHellmanData bytes], [diffieHellmanData length]);
if (status != noErr)
{
[self closeWithError:[self otherError:@"Error in SSLSetDiffieHellmanParams"]];
return;
}
}
else if (value)
{
NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLDiffieHellmanParameters. Value must be of type NSData.");
[self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLDiffieHellmanParameters."]];
return;
}
#endif
// 10. kCFStreamSSLCertificates
value = [tlsSettings objectForKey:GCDAsyncSocketSSLALPN];
if ([value isKindOfClass:[NSArray class]])
{
if (@available(iOS 11.0, macOS 10.13, tvOS 11.0, *))
{
CFArrayRef protocols = (__bridge CFArrayRef)((NSArray *) value);
status = SSLSetALPNProtocols(sslContext, protocols);
if (status != noErr)
{
[self closeWithError:[self otherError:@"Error in SSLSetALPNProtocols"]];
return;
}
}
else
{
NSAssert(NO, @"Security option unavailable - GCDAsyncSocketSSLALPN"
@" - iOS 11.0, macOS 10.13 required");
[self closeWithError:[self otherError:@"Security option unavailable - GCDAsyncSocketSSLALPN"]];
}
}
else if (value)
{
NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLALPN. Value must be of type NSArray.");
[self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLALPN."]];
return;
}
// DEPRECATED checks
// 10. kCFStreamSSLAllowsAnyRoot
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsAnyRoot];
#pragma clang diagnostic pop
if (value)
{
NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsAnyRoot"
@" - You must use manual trust evaluation");
[self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsAnyRoot"]];
return;
}
// 11. kCFStreamSSLAllowsExpiredRoots
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsExpiredRoots];
#pragma clang diagnostic pop
if (value)
{
NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsExpiredRoots"
@" - You must use manual trust evaluation");
[self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsExpiredRoots"]];
return;
}
// 12. kCFStreamSSLValidatesCertificateChain
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLValidatesCertificateChain];
#pragma clang diagnostic pop
if (value)
{
NSAssert(NO, @"Security option unavailable - kCFStreamSSLValidatesCertificateChain"
@" - You must use manual trust evaluation");
[self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLValidatesCertificateChain"]];
return;
}
// 13. kCFStreamSSLAllowsExpiredCertificates
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsExpiredCertificates];
#pragma clang diagnostic pop
if (value)
{
NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsExpiredCertificates"
@" - You must use manual trust evaluation");
[self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsExpiredCertificates"]];
return;
}
// 14. kCFStreamSSLLevel
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLLevel];
#pragma clang diagnostic pop
if (value)
{
NSAssert(NO, @"Security option unavailable - kCFStreamSSLLevel"
@" - You must use GCDAsyncSocketSSLProtocolVersionMin & GCDAsyncSocketSSLProtocolVersionMax");
[self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLLevel"]];
return;
}
// Setup the sslPreBuffer
//
// Any data in the preBuffer needs to be moved into the sslPreBuffer,
// as this data is now part of the secure read stream.
sslPreBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)];
size_t preBufferLength = [preBuffer availableBytes];
if (preBufferLength > 0)
{
[sslPreBuffer ensureCapacityForWrite:preBufferLength];
memcpy([sslPreBuffer writeBuffer], [preBuffer readBuffer], preBufferLength);
[preBuffer didRead:preBufferLength];
[sslPreBuffer didWrite:preBufferLength];
}
sslErrCode = lastSSLHandshakeError = noErr;
// Start the SSL Handshake process
[self ssl_continueSSLHandshake];
}
- (void)ssl_continueSSLHandshake
{
LogTrace();
// If the return value is noErr, the session is ready for normal secure communication.
// If the return value is errSSLWouldBlock, the SSLHandshake function must be called again.
// If the return value is errSSLServerAuthCompleted, we ask delegate if we should trust the
// server and then call SSLHandshake again to resume the handshake or close the connection
// errSSLPeerBadCert SSL error.
// Otherwise, the return value indicates an error code.
OSStatus status = SSLHandshake(sslContext);
lastSSLHandshakeError = status;
if (status == noErr)
{
LogVerbose(@"SSLHandshake complete");
flags &= ~kStartingReadTLS;
flags &= ~kStartingWriteTLS;
flags |= kSocketSecure;
__strong id theDelegate = delegate;
if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidSecure:)])
{
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate socketDidSecure:self];
}});
}
[self endCurrentRead];
[self endCurrentWrite];
[self maybeDequeueRead];
[self maybeDequeueWrite];
}
else if (status == errSSLPeerAuthCompleted)
{
LogVerbose(@"SSLHandshake peerAuthCompleted - awaiting delegate approval");
__block SecTrustRef trust = NULL;
status = SSLCopyPeerTrust(sslContext, &trust);
if (status != noErr)
{
[self closeWithError:[self sslError:status]];
return;
}
int aStateIndex = stateIndex;
dispatch_queue_t theSocketQueue = socketQueue;
__weak GCDAsyncSocket *weakSelf = self;
void (^comletionHandler)(BOOL) = ^(BOOL shouldTrust){ @autoreleasepool {
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
dispatch_async(theSocketQueue, ^{ @autoreleasepool {
if (trust) {
CFRelease(trust);
trust = NULL;
}
__strong GCDAsyncSocket *strongSelf = weakSelf;
if (strongSelf)
{
[strongSelf ssl_shouldTrustPeer:shouldTrust stateIndex:aStateIndex];
}
}});
#pragma clang diagnostic pop
}};
__strong id theDelegate = delegate;
if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReceiveTrust:completionHandler:)])
{
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate socket:self didReceiveTrust:trust completionHandler:comletionHandler];
}});
}
else
{
if (trust) {
CFRelease(trust);
trust = NULL;
}
NSString *msg = @"GCDAsyncSocketManuallyEvaluateTrust specified in tlsSettings,"
@" but delegate doesn't implement socket:shouldTrustPeer:";
[self closeWithError:[self otherError:msg]];
return;
}
}
else if (status == errSSLWouldBlock)
{
LogVerbose(@"SSLHandshake continues...");
// Handshake continues...
//
// This method will be called again from doReadData or doWriteData.
}
else
{
[self closeWithError:[self sslError:status]];
}
}
- (void)ssl_shouldTrustPeer:(BOOL)shouldTrust stateIndex:(int)aStateIndex
{
LogTrace();
if (aStateIndex != stateIndex)
{
LogInfo(@"Ignoring ssl_shouldTrustPeer - invalid state (maybe disconnected)");
// One of the following is true
// - the socket was disconnected
// - the startTLS operation timed out
// - the completionHandler was already invoked once
return;
}
// Increment stateIndex to ensure completionHandler can only be called once.
stateIndex++;
if (shouldTrust)
{
NSAssert(lastSSLHandshakeError == errSSLPeerAuthCompleted, @"ssl_shouldTrustPeer called when last error is %d and not errSSLPeerAuthCompleted", (int)lastSSLHandshakeError);
[self ssl_continueSSLHandshake];
}
else
{
[self closeWithError:[self sslError:errSSLPeerBadCert]];
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Security via CFStream
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if TARGET_OS_IPHONE
- (void)cf_finishSSLHandshake
{
LogTrace();
if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS))
{
flags &= ~kStartingReadTLS;
flags &= ~kStartingWriteTLS;
flags |= kSocketSecure;
__strong id theDelegate = delegate;
if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidSecure:)])
{
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate socketDidSecure:self];
}});
}
[self endCurrentRead];
[self endCurrentWrite];
[self maybeDequeueRead];
[self maybeDequeueWrite];
}
}
- (void)cf_abortSSLHandshake:(NSError *)error
{
LogTrace();
if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS))
{
flags &= ~kStartingReadTLS;
flags &= ~kStartingWriteTLS;
[self closeWithError:error];
}
}
- (void)cf_startTLS
{
LogTrace();
LogVerbose(@"Starting TLS (via CFStream)...");
if ([preBuffer availableBytes] > 0)
{
NSString *msg = @"Invalid TLS transition. Handshake has already been read from socket.";
[self closeWithError:[self otherError:msg]];
return;
}
[self suspendReadSource];
[self suspendWriteSource];
socketFDBytesAvailable = 0;
flags &= ~kSocketCanAcceptBytes;
flags &= ~kSecureSocketHasBytesAvailable;
flags |= kUsingCFStreamForTLS;
if (![self createReadAndWriteStream])
{
[self closeWithError:[self otherError:@"Error in CFStreamCreatePairWithSocket"]];
return;
}
if (![self registerForStreamCallbacksIncludingReadWrite:YES])
{
[self closeWithError:[self otherError:@"Error in CFStreamSetClient"]];
return;
}
if (![self addStreamsToRunLoop])
{
[self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]];
return;
}
NSAssert([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]], @"Invalid read packet for startTLS");
NSAssert([currentWrite isKindOfClass:[GCDAsyncSpecialPacket class]], @"Invalid write packet for startTLS");
GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead;
CFDictionaryRef tlsSettings = (__bridge CFDictionaryRef)tlsPacket->tlsSettings;
// Getting an error concerning kCFStreamPropertySSLSettings ?
// You need to add the CFNetwork framework to your iOS application.
BOOL r1 = CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, tlsSettings);
BOOL r2 = CFWriteStreamSetProperty(writeStream, kCFStreamPropertySSLSettings, tlsSettings);
// For some reason, starting around the time of iOS 4.3,
// the first call to set the kCFStreamPropertySSLSettings will return true,
// but the second will return false.
//
// Order doesn't seem to matter.
// So you could call CFReadStreamSetProperty and then CFWriteStreamSetProperty, or you could reverse the order.
// Either way, the first call will return true, and the second returns false.
//
// Interestingly, this doesn't seem to affect anything.
// Which is not altogether unusual, as the documentation seems to suggest that (for many settings)
// setting it on one side of the stream automatically sets it for the other side of the stream.
//
// Although there isn't anything in the documentation to suggest that the second attempt would fail.
//
// Furthermore, this only seems to affect streams that are negotiating a security upgrade.
// In other words, the socket gets connected, there is some back-and-forth communication over the unsecure
// connection, and then a startTLS is issued.
// So this mostly affects newer protocols (XMPP, IMAP) as opposed to older protocols (HTTPS).
if (!r1 && !r2) // Yes, the && is correct - workaround for apple bug.
{
[self closeWithError:[self otherError:@"Error in CFStreamSetProperty"]];
return;
}
if (![self openStreams])
{
[self closeWithError:[self otherError:@"Error in CFStreamOpen"]];
return;
}
LogVerbose(@"Waiting for SSL Handshake to complete...");
}
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark CFStream
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if TARGET_OS_IPHONE
+ (void)ignore:(id)_
{}
+ (void)startCFStreamThreadIfNeeded
{
LogTrace();
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
cfstreamThreadRetainCount = 0;
cfstreamThreadSetupQueue = dispatch_queue_create("GCDAsyncSocket-CFStreamThreadSetup", DISPATCH_QUEUE_SERIAL);
});
dispatch_sync(cfstreamThreadSetupQueue, ^{ @autoreleasepool {
if (++cfstreamThreadRetainCount == 1)
{
cfstreamThread = [[NSThread alloc] initWithTarget:self
selector:@selector(cfstreamThread:)
object:nil];
[cfstreamThread start];
}
}});
}
+ (void)stopCFStreamThreadIfNeeded
{
LogTrace();
// The creation of the cfstreamThread is relatively expensive.
// So we'd like to keep it available for recycling.
// However, there's a tradeoff here, because it shouldn't remain alive forever.
// So what we're going to do is use a little delay before taking it down.
// This way it can be reused properly in situations where multiple sockets are continually in flux.
int delayInSeconds = 30;
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(when, cfstreamThreadSetupQueue, ^{ @autoreleasepool {
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"
if (cfstreamThreadRetainCount == 0)
{
LogWarn(@"Logic error concerning cfstreamThread start / stop");
return_from_block;
}
if (--cfstreamThreadRetainCount == 0)
{
[cfstreamThread cancel]; // set isCancelled flag
// wake up the thread
[[self class] performSelector:@selector(ignore:)
onThread:cfstreamThread
withObject:[NSNull null]
waitUntilDone:NO];
cfstreamThread = nil;
}
#pragma clang diagnostic pop
}});
}
+ (void)cfstreamThread:(id)unused { @autoreleasepool
{
[[NSThread currentThread] setName:GCDAsyncSocketThreadName];
LogInfo(@"CFStreamThread: Started");
// We can't run the run loop unless it has an associated input source or a timer.
// So we'll just create a timer that will never fire - unless the server runs for decades.
[NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow]
target:self
selector:@selector(ignore:)
userInfo:nil
repeats:YES];
NSThread *currentThread = [NSThread currentThread];
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
BOOL isCancelled = [currentThread isCancelled];
while (!isCancelled && [currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]])
{
isCancelled = [currentThread isCancelled];
}
LogInfo(@"CFStreamThread: Stopped");
}}
+ (void)scheduleCFStreams:(GCDAsyncSocket *)asyncSocket
{
LogTrace();
NSAssert([NSThread currentThread] == cfstreamThread, @"Invoked on wrong thread");
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
if (asyncSocket->readStream)
CFReadStreamScheduleWithRunLoop(asyncSocket->readStream, runLoop, kCFRunLoopDefaultMode);
if (asyncSocket->writeStream)
CFWriteStreamScheduleWithRunLoop(asyncSocket->writeStream, runLoop, kCFRunLoopDefaultMode);
}
+ (void)unscheduleCFStreams:(GCDAsyncSocket *)asyncSocket
{
LogTrace();
NSAssert([NSThread currentThread] == cfstreamThread, @"Invoked on wrong thread");
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
if (asyncSocket->readStream)
CFReadStreamUnscheduleFromRunLoop(asyncSocket->readStream, runLoop, kCFRunLoopDefaultMode);
if (asyncSocket->writeStream)
CFWriteStreamUnscheduleFromRunLoop(asyncSocket->writeStream, runLoop, kCFRunLoopDefaultMode);
}
static void CFReadStreamCallback (CFReadStreamRef stream, CFStreamEventType type, void *pInfo)
{
GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)pInfo;
switch(type)
{
case kCFStreamEventHasBytesAvailable:
{
dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool {
LogCVerbose(@"CFReadStreamCallback - HasBytesAvailable");
if (asyncSocket->readStream != stream)
return_from_block;
if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS))
{
// If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie.
// (A callback related to the tcp stream, but not to the SSL layer).
if (CFReadStreamHasBytesAvailable(asyncSocket->readStream))
{
asyncSocket->flags |= kSecureSocketHasBytesAvailable;
[asyncSocket cf_finishSSLHandshake];
}
}
else
{
asyncSocket->flags |= kSecureSocketHasBytesAvailable;
[asyncSocket doReadData];
}
}});
break;
}
default:
{
NSError *error = (__bridge_transfer NSError *)CFReadStreamCopyError(stream);
if (error == nil && type == kCFStreamEventEndEncountered)
{
error = [asyncSocket connectionClosedError];
}
dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool {
LogCVerbose(@"CFReadStreamCallback - Other");
if (asyncSocket->readStream != stream)
return_from_block;
if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS))
{
[asyncSocket cf_abortSSLHandshake:error];
}
else
{
[asyncSocket closeWithError:error];
}
}});
break;
}
}
}
static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType type, void *pInfo)
{
GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)pInfo;
switch(type)
{
case kCFStreamEventCanAcceptBytes:
{
dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool {
LogCVerbose(@"CFWriteStreamCallback - CanAcceptBytes");
if (asyncSocket->writeStream != stream)
return_from_block;
if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS))
{
// If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie.
// (A callback related to the tcp stream, but not to the SSL layer).
if (CFWriteStreamCanAcceptBytes(asyncSocket->writeStream))
{
asyncSocket->flags |= kSocketCanAcceptBytes;
[asyncSocket cf_finishSSLHandshake];
}
}
else
{
asyncSocket->flags |= kSocketCanAcceptBytes;
[asyncSocket doWriteData];
}
}});
break;
}
default:
{
NSError *error = (__bridge_transfer NSError *)CFWriteStreamCopyError(stream);
if (error == nil && type == kCFStreamEventEndEncountered)
{
error = [asyncSocket connectionClosedError];
}
dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool {
LogCVerbose(@"CFWriteStreamCallback - Other");
if (asyncSocket->writeStream != stream)
return_from_block;
if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS))
{
[asyncSocket cf_abortSSLHandshake:error];
}
else
{
[asyncSocket closeWithError:error];
}
}});
break;
}
}
}
- (BOOL)createReadAndWriteStream
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
if (readStream || writeStream)
{
// Streams already created
return YES;
}
int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
if (socketFD == SOCKET_NULL)
{
// Cannot create streams without a file descriptor
return NO;
}
if (![self isConnected])
{
// Cannot create streams until file descriptor is connected
return NO;
}
LogVerbose(@"Creating read and write stream...");
CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socketFD, &readStream, &writeStream);
// The kCFStreamPropertyShouldCloseNativeSocket property should be false by default (for our case).
// But let's not take any chances.
if (readStream)
CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
if (writeStream)
CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
if ((readStream == NULL) || (writeStream == NULL))
{
LogWarn(@"Unable to create read and write stream...");
if (readStream)
{
CFReadStreamClose(readStream);
CFRelease(readStream);
readStream = NULL;
}
if (writeStream)
{
CFWriteStreamClose(writeStream);
CFRelease(writeStream);
writeStream = NULL;
}
return NO;
}
return YES;
}
- (BOOL)registerForStreamCallbacksIncludingReadWrite:(BOOL)includeReadWrite
{
LogVerbose(@"%@ %@", THIS_METHOD, (includeReadWrite ? @"YES" : @"NO"));
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null");
streamContext.version = 0;
streamContext.info = (__bridge void *)(self);
streamContext.retain = nil;
streamContext.release = nil;
streamContext.copyDescription = nil;
CFOptionFlags readStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
if (includeReadWrite)
readStreamEvents |= kCFStreamEventHasBytesAvailable;
if (!CFReadStreamSetClient(readStream, readStreamEvents, &CFReadStreamCallback, &streamContext))
{
return NO;
}
CFOptionFlags writeStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
if (includeReadWrite)
writeStreamEvents |= kCFStreamEventCanAcceptBytes;
if (!CFWriteStreamSetClient(writeStream, writeStreamEvents, &CFWriteStreamCallback, &streamContext))
{
return NO;
}
return YES;
}
- (BOOL)addStreamsToRunLoop
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null");
if (!(flags & kAddedStreamsToRunLoop))
{
LogVerbose(@"Adding streams to runloop...");
[[self class] startCFStreamThreadIfNeeded];
dispatch_sync(cfstreamThreadSetupQueue, ^{
[[self class] performSelector:@selector(scheduleCFStreams:)
onThread:cfstreamThread
withObject:self
waitUntilDone:YES];
});
flags |= kAddedStreamsToRunLoop;
}
return YES;
}
- (void)removeStreamsFromRunLoop
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null");
if (flags & kAddedStreamsToRunLoop)
{
LogVerbose(@"Removing streams from runloop...");
dispatch_sync(cfstreamThreadSetupQueue, ^{
[[self class] performSelector:@selector(unscheduleCFStreams:)
onThread:cfstreamThread
withObject:self
waitUntilDone:YES];
});
[[self class] stopCFStreamThreadIfNeeded];
flags &= ~kAddedStreamsToRunLoop;
}
}
- (BOOL)openStreams
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null");
CFStreamStatus readStatus = CFReadStreamGetStatus(readStream);
CFStreamStatus writeStatus = CFWriteStreamGetStatus(writeStream);
if ((readStatus == kCFStreamStatusNotOpen) || (writeStatus == kCFStreamStatusNotOpen))
{
LogVerbose(@"Opening read and write stream...");
BOOL r1 = CFReadStreamOpen(readStream);
BOOL r2 = CFWriteStreamOpen(writeStream);
if (!r1 || !r2)
{
LogError(@"Error in CFStreamOpen");
return NO;
}
}
return YES;
}
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Advanced
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* See header file for big discussion of this method.
**/
- (BOOL)autoDisconnectOnClosedReadStream
{
// Note: YES means kAllowHalfDuplexConnection is OFF
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
return ((config & kAllowHalfDuplexConnection) == 0);
}
else
{
__block BOOL result;
dispatch_sync(socketQueue, ^{
result = ((self->config & kAllowHalfDuplexConnection) == 0);
});
return result;
}
}
/**
* See header file for big discussion of this method.
**/
- (void)setAutoDisconnectOnClosedReadStream:(BOOL)flag
{
// Note: YES means kAllowHalfDuplexConnection is OFF
dispatch_block_t block = ^{
if (flag)
self->config &= ~kAllowHalfDuplexConnection;
else
self->config |= kAllowHalfDuplexConnection;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
/**
* See header file for big discussion of this method.
**/
- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketNewTargetQueue
{
void *nonNullUnusedPointer = (__bridge void *)self;
dispatch_queue_set_specific(socketNewTargetQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL);
}
/**
* See header file for big discussion of this method.
**/
- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketOldTargetQueue
{
dispatch_queue_set_specific(socketOldTargetQueue, IsOnSocketQueueOrTargetQueueKey, NULL, NULL);
}
/**
* See header file for big discussion of this method.
**/
- (void)performBlock:(dispatch_block_t)block
{
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
}
/**
* Questions? Have you read the header file?
**/
- (int)socketFD
{
if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
return SOCKET_NULL;
}
if (socket4FD != SOCKET_NULL)
return socket4FD;
else
return socket6FD;
}
/**
* Questions? Have you read the header file?
**/
- (int)socket4FD
{
if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
return SOCKET_NULL;
}
return socket4FD;
}
/**
* Questions? Have you read the header file?
**/
- (int)socket6FD
{
if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
return SOCKET_NULL;
}
return socket6FD;
}
#if TARGET_OS_IPHONE
/**
* Questions? Have you read the header file?
**/
- (CFReadStreamRef)readStream
{
if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
return NULL;
}
if (readStream == NULL)
[self createReadAndWriteStream];
return readStream;
}
/**
* Questions? Have you read the header file?
**/
- (CFWriteStreamRef)writeStream
{
if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
return NULL;
}
if (writeStream == NULL)
[self createReadAndWriteStream];
return writeStream;
}
- (BOOL)enableBackgroundingOnSocketWithCaveat:(BOOL)caveat
{
if (![self createReadAndWriteStream])
{
// Error occurred creating streams (perhaps socket isn't open)
return NO;
}
BOOL r1, r2;
LogVerbose(@"Enabling backgrouding on socket");
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
r1 = CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
r2 = CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
#pragma clang diagnostic pop
if (!r1 || !r2)
{
return NO;
}
if (!caveat)
{
if (![self openStreams])
{
return NO;
}
}
return YES;
}
/**
* Questions? Have you read the header file?
**/
- (BOOL)enableBackgroundingOnSocket
{
LogTrace();
if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
return NO;
}
return [self enableBackgroundingOnSocketWithCaveat:NO];
}
- (BOOL)enableBackgroundingOnSocketWithCaveat // Deprecated in iOS 4.???
{
// This method was created as a workaround for a bug in iOS.
// Apple has since fixed this bug.
// I'm not entirely sure which version of iOS they fixed it in...
LogTrace();
if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
return NO;
}
return [self enableBackgroundingOnSocketWithCaveat:YES];
}
#endif
- (SSLContextRef)sslContext
{
if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
return NULL;
}
return sslContext;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Class Utilities
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ (NSMutableArray *)lookupHost:(NSString *)host port:(uint16_t)port error:(NSError **)errPtr
{
LogTrace();
NSMutableArray *addresses = nil;
NSError *error = nil;
if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"])
{
// Use LOOPBACK address
struct sockaddr_in nativeAddr4;
nativeAddr4.sin_len = sizeof(struct sockaddr_in);
nativeAddr4.sin_family = AF_INET;
nativeAddr4.sin_port = htons(port);
nativeAddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
memset(&(nativeAddr4.sin_zero), 0, sizeof(nativeAddr4.sin_zero));
struct sockaddr_in6 nativeAddr6;
nativeAddr6.sin6_len = sizeof(struct sockaddr_in6);
nativeAddr6.sin6_family = AF_INET6;
nativeAddr6.sin6_port = htons(port);
nativeAddr6.sin6_flowinfo = 0;
nativeAddr6.sin6_addr = in6addr_loopback;
nativeAddr6.sin6_scope_id = 0;
// Wrap the native address structures
NSData *address4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
NSData *address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
addresses = [NSMutableArray arrayWithCapacity:2];
[addresses addObject:address4];
[addresses addObject:address6];
}
else
{
NSString *portStr = [NSString stringWithFormat:@"%hu", port];
struct addrinfo hints, *res, *res0;
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0);
if (gai_error)
{
error = [self gaiError:gai_error];
}
else
{
NSUInteger capacity = 0;
for (res = res0; res; res = res->ai_next)
{
if (res->ai_family == AF_INET || res->ai_family == AF_INET6) {
capacity++;
}
}
addresses = [NSMutableArray arrayWithCapacity:capacity];
for (res = res0; res; res = res->ai_next)
{
if (res->ai_family == AF_INET)
{
// Found IPv4 address.
// Wrap the native address structure, and add to results.
NSData *address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
[addresses addObject:address4];
}
else if (res->ai_family == AF_INET6)
{
// Fixes connection issues with IPv6
// https://github.com/robbiehanson/CocoaAsyncSocket/issues/429#issuecomment-222477158
// Found IPv6 address.
// Wrap the native address structure, and add to results.
struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)(void *)res->ai_addr;
in_port_t *portPtr = &sockaddr->sin6_port;
if ((portPtr != NULL) && (*portPtr == 0)) {
*portPtr = htons(port);
}
NSData *address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
[addresses addObject:address6];
}
}
freeaddrinfo(res0);
if ([addresses count] == 0)
{
error = [self gaiError:EAI_FAIL];
}
}
}
if (errPtr) *errPtr = error;
return addresses;
}
+ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4
{
char addrBuf[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET, &pSockaddr4->sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
{
addrBuf[0] = '\0';
}
return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
}
+ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6
{
char addrBuf[INET6_ADDRSTRLEN];
if (inet_ntop(AF_INET6, &pSockaddr6->sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
{
addrBuf[0] = '\0';
}
return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
}
+ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4
{
return ntohs(pSockaddr4->sin_port);
}
+ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6
{
return ntohs(pSockaddr6->sin6_port);
}
+ (NSURL *)urlFromSockaddrUN:(const struct sockaddr_un *)pSockaddr
{
NSString *path = [NSString stringWithUTF8String:pSockaddr->sun_path];
return [NSURL fileURLWithPath:path];
}
+ (NSString *)hostFromAddress:(NSData *)address
{
NSString *host;
if ([self getHost:&host port:NULL fromAddress:address])
return host;
else
return nil;
}
+ (uint16_t)portFromAddress:(NSData *)address
{
uint16_t port;
if ([self getHost:NULL port:&port fromAddress:address])
return port;
else
return 0;
}
+ (BOOL)isIPv4Address:(NSData *)address
{
if ([address length] >= sizeof(struct sockaddr))
{
const struct sockaddr *sockaddrX = [address bytes];
if (sockaddrX->sa_family == AF_INET) {
return YES;
}
}
return NO;
}
+ (BOOL)isIPv6Address:(NSData *)address
{
if ([address length] >= sizeof(struct sockaddr))
{
const struct sockaddr *sockaddrX = [address bytes];
if (sockaddrX->sa_family == AF_INET6) {
return YES;
}
}
return NO;
}
+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address
{
return [self getHost:hostPtr port:portPtr family:NULL fromAddress:address];
}
+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr family:(sa_family_t *)afPtr fromAddress:(NSData *)address
{
if ([address length] >= sizeof(struct sockaddr))
{
const struct sockaddr *sockaddrX = [address bytes];
if (sockaddrX->sa_family == AF_INET)
{
if ([address length] >= sizeof(struct sockaddr_in))
{
struct sockaddr_in sockaddr4;
memcpy(&sockaddr4, sockaddrX, sizeof(sockaddr4));
if (hostPtr) *hostPtr = [self hostFromSockaddr4:&sockaddr4];
if (portPtr) *portPtr = [self portFromSockaddr4:&sockaddr4];
if (afPtr) *afPtr = AF_INET;
return YES;
}
}
else if (sockaddrX->sa_family == AF_INET6)
{
if ([address length] >= sizeof(struct sockaddr_in6))
{
struct sockaddr_in6 sockaddr6;
memcpy(&sockaddr6, sockaddrX, sizeof(sockaddr6));
if (hostPtr) *hostPtr = [self hostFromSockaddr6:&sockaddr6];
if (portPtr) *portPtr = [self portFromSockaddr6:&sockaddr6];
if (afPtr) *afPtr = AF_INET6;
return YES;
}
}
}
return NO;
}
+ (NSData *)CRLFData
{
return [NSData dataWithBytes:"\x0D\x0A" length:2];
}
+ (NSData *)CRData
{
return [NSData dataWithBytes:"\x0D" length:1];
}
+ (NSData *)LFData
{
return [NSData dataWithBytes:"\x0A" length:1];
}
+ (NSData *)ZeroData
{
return [NSData dataWithBytes:"" length:1];
}
@end
================================================
FILE: Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncUdpSocket.h
================================================
//
// GCDAsyncUdpSocket
//
// This class is in the public domain.
// Originally created by Robbie Hanson of Deusty LLC.
// Updated and maintained by Deusty LLC and the Apple development community.
//
// https://github.com/robbiehanson/CocoaAsyncSocket
//
#import
#import
#import
#import
NS_ASSUME_NONNULL_BEGIN
extern NSString *const GCDAsyncUdpSocketException;
extern NSString *const GCDAsyncUdpSocketErrorDomain;
extern NSString *const GCDAsyncUdpSocketQueueName;
extern NSString *const GCDAsyncUdpSocketThreadName;
typedef NS_ERROR_ENUM(GCDAsyncUdpSocketErrorDomain, GCDAsyncUdpSocketError) {
GCDAsyncUdpSocketNoError = 0, // Never used
GCDAsyncUdpSocketBadConfigError, // Invalid configuration
GCDAsyncUdpSocketBadParamError, // Invalid parameter was passed
GCDAsyncUdpSocketSendTimeoutError, // A send operation timed out
GCDAsyncUdpSocketClosedError, // The socket was closed
GCDAsyncUdpSocketOtherError, // Description provided in userInfo
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@class GCDAsyncUdpSocket;
@protocol GCDAsyncUdpSocketDelegate
@optional
/**
* By design, UDP is a connectionless protocol, and connecting is not needed.
* However, you may optionally choose to connect to a particular host for reasons
* outlined in the documentation for the various connect methods listed above.
*
* This method is called if one of the connect methods are invoked, and the connection is successful.
**/
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address;
/**
* By design, UDP is a connectionless protocol, and connecting is not needed.
* However, you may optionally choose to connect to a particular host for reasons
* outlined in the documentation for the various connect methods listed above.
*
* This method is called if one of the connect methods are invoked, and the connection fails.
* This may happen, for example, if a domain name is given for the host and the domain name is unable to be resolved.
**/
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError * _Nullable)error;
/**
* Called when the datagram with the given tag has been sent.
**/
- (void)udpSocket:(GCDAsyncUdpSocket *)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)udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError * _Nullable)error;
/**
* Called when the socket has received the requested datagram.
**/
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data
fromAddress:(NSData *)address
withFilterContext:(nullable id)filterContext;
/**
* Called when the socket is closed.
**/
- (void)udpSocketDidClose:(GCDAsyncUdpSocket *)sock withError:(NSError * _Nullable)error;
@end
/**
* You may optionally set a receive filter for the socket.
* A filter can provide several useful features:
*
* 1. Many times udp packets need to be parsed.
* Since the filter can run in its own independent queue, you can parallelize this parsing quite easily.
* The end result is a parallel socket io, datagram parsing, and packet processing.
*
* 2. Many times udp packets are discarded because they are duplicate/unneeded/unsolicited.
* The filter can prevent such packets from arriving at the delegate.
* And because the filter can run in its own independent queue, this doesn't slow down the delegate.
*
* - Since the udp protocol does not guarantee delivery, udp packets may be lost.
* Many protocols built atop udp thus provide various resend/re-request algorithms.
* This sometimes results in duplicate packets arriving.
* A filter may allow you to architect the duplicate detection code to run in parallel to normal processing.
*
* - Since the udp socket may be connectionless, its possible for unsolicited packets to arrive.
* Such packets need to be ignored.
*
* 3. Sometimes traffic shapers are needed to simulate real world environments.
* A filter allows you to write custom code to simulate such environments.
* The ability to code this yourself is especially helpful when your simulated environment
* is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router),
* or the system tools to handle this aren't available (e.g. on a mobile device).
*
* @param data - The packet that was received.
* @param address - The address the data was received from.
* See utilities section for methods to extract info from address.
* @param context - Out parameter you may optionally set, which will then be passed to the delegate method.
* For example, filter block can parse the data and then,
* pass the parsed data to the delegate.
*
* @returns - YES if the received packet should be passed onto the delegate.
* NO if the received packet should be discarded, and not reported to the delegete.
*
* Example:
*
* GCDAsyncUdpSocketReceiveFilterBlock filter = ^BOOL (NSData *data, NSData *address, id *context) {
*
* MyProtocolMessage *msg = [MyProtocol parseMessage:data];
*
* *context = response;
* return (response != nil);
* };
* [udpSocket setReceiveFilter:filter withQueue:myParsingQueue];
*
**/
typedef BOOL (^GCDAsyncUdpSocketReceiveFilterBlock)(NSData *data, NSData *address, id __nullable * __nonnull context);
/**
* You may optionally set a send filter for the socket.
* A filter can provide several interesting possibilities:
*
* 1. Optional caching of resolved addresses for domain names.
* The cache could later be consulted, resulting in fewer system calls to getaddrinfo.
*
* 2. Reusable modules of code for bandwidth monitoring.
*
* 3. Sometimes traffic shapers are needed to simulate real world environments.
* A filter allows you to write custom code to simulate such environments.
* The ability to code this yourself is especially helpful when your simulated environment
* is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router),
* or the system tools to handle this aren't available (e.g. on a mobile device).
*
* @param data - The packet that was received.
* @param address - The address the data was received from.
* See utilities section for methods to extract info from address.
* @param tag - The tag that was passed in the send method.
*
* @returns - YES if the packet should actually be sent over the socket.
* NO if the packet should be silently dropped (not sent over the socket).
*
* Regardless of the return value, the delegate will be informed that the packet was successfully sent.
*
**/
typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, long tag);
@interface GCDAsyncUdpSocket : NSObject
/**
* GCDAsyncUdpSocket uses the standard delegate paradigm,
* but executes all delegate callbacks on a given delegate dispatch queue.
* This allows for maximum concurrency, while at the same time providing easy thread safety.
*
* You MUST set a delegate AND delegate dispatch queue before attempting to
* use the socket, or you will get an error.
*
* The socket queue is optional.
* If you pass NULL, GCDAsyncSocket will automatically create its own socket queue.
* If you choose to provide a socket queue, the socket queue must not be a concurrent queue,
* then please see the discussion for the method markSocketQueueTargetQueue.
*
* The delegate queue and socket queue can optionally be the same.
**/
- (instancetype)init;
- (instancetype)initWithSocketQueue:(nullable dispatch_queue_t)sq;
- (instancetype)initWithDelegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq;
- (instancetype)initWithDelegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq NS_DESIGNATED_INITIALIZER;
#pragma mark Configuration
- (nullable id)delegate;
- (void)setDelegate:(nullable id)delegate;
- (void)synchronouslySetDelegate:(nullable id)delegate;
- (nullable dispatch_queue_t)delegateQueue;
- (void)setDelegateQueue:(nullable dispatch_queue_t)delegateQueue;
- (void)synchronouslySetDelegateQueue:(nullable dispatch_queue_t)delegateQueue;
- (void)getDelegate:(id __nullable * __nullable)delegatePtr delegateQueue:(dispatch_queue_t __nullable * __nullable)delegateQueuePtr;
- (void)setDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue;
- (void)synchronouslySetDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue;
/**
* By default, both IPv4 and IPv6 are enabled.
*
* This means GCDAsyncUdpSocket automatically supports both protocols,
* and can send to IPv4 or IPv6 addresses,
* as well as receive over IPv4 and IPv6.
*
* For operations that require DNS resolution, GCDAsyncUdpSocket supports both IPv4 and IPv6.
* If a DNS lookup returns only IPv4 results, GCDAsyncUdpSocket will automatically use IPv4.
* If a DNS lookup returns only IPv6 results, GCDAsyncUdpSocket will automatically use IPv6.
* If a DNS lookup returns both IPv4 and IPv6 results, then the protocol used depends on the configured preference.
* If IPv4 is preferred, then IPv4 is used.
* If IPv6 is preferred, then IPv6 is used.
* If neutral, then the first IP version in the resolved array will be used.
*
* Starting with Mac OS X 10.7 Lion and iOS 5, the default IP preference is neutral.
* On prior systems the default IP preference is IPv4.
**/
- (BOOL)isIPv4Enabled;
- (void)setIPv4Enabled:(BOOL)flag;
- (BOOL)isIPv6Enabled;
- (void)setIPv6Enabled:(BOOL)flag;
- (BOOL)isIPv4Preferred;
- (BOOL)isIPv6Preferred;
- (BOOL)isIPVersionNeutral;
- (void)setPreferIPv4;
- (void)setPreferIPv6;
- (void)setIPVersionNeutral;
/**
* Gets/Sets the maximum size of the buffer that will be allocated for receive operations.
* The default maximum size is 65535 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.
*
* Since the OS/GCD notifies us of the size of each received UDP packet,
* the actual allocated buffer size for each packet is exact.
* And in practice the size of UDP packets is generally much smaller than the max.
* 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.
**/
- (uint16_t)maxReceiveIPv4BufferSize;
- (void)setMaxReceiveIPv4BufferSize:(uint16_t)max;
- (uint32_t)maxReceiveIPv6BufferSize;
- (void)setMaxReceiveIPv6BufferSize:(uint32_t)max;
/**
* Gets/Sets the maximum size of the buffer that will be allocated for send operations.
* The default maximum size is 65535 bytes.
*
* Given that a typical link MTU is 1500 bytes, a large UDP datagram will have to be
* fragmented, and that’s both expensive and risky (if one fragment goes missing, the
* entire datagram is lost). You are much better off sending a large number of smaller
* UDP datagrams, preferably using a path MTU algorithm to avoid fragmentation.
*
* You must set it before the sockt is created otherwise it won't work.
*
**/
- (uint16_t)maxSendBufferSize;
- (void)setMaxSendBufferSize:(uint16_t)max;
/**
* User data allows you to associate arbitrary information with the socket.
* This data is not used internally in any way.
**/
- (nullable id)userData;
- (void)setUserData:(nullable id)arbitraryUserData;
#pragma mark Diagnostics
/**
* Returns the local address info for the socket.
*
* The localAddress method returns a sockaddr structure wrapped in a NSData object.
* The localHost method returns the human readable IP address as a string.
*
* Note: Address info may not be available until after the socket has been binded, connected
* or until after data has been sent.
**/
- (nullable NSData *)localAddress;
- (nullable NSString *)localHost;
- (uint16_t)localPort;
- (nullable NSData *)localAddress_IPv4;
- (nullable NSString *)localHost_IPv4;
- (uint16_t)localPort_IPv4;
- (nullable NSData *)localAddress_IPv6;
- (nullable NSString *)localHost_IPv6;
- (uint16_t)localPort_IPv6;
/**
* Returns the remote address info for the socket.
*
* The connectedAddress method returns a sockaddr structure wrapped in a NSData object.
* The connectedHost method returns the human readable IP address as a string.
*
* 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.
* If the socket is not connected, these methods will return nil / 0.
**/
- (nullable NSData *)connectedAddress;
- (nullable NSString *)connectedHost;
- (uint16_t)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 is IPv4.
*
* By default this will be true, unless:
* - IPv4 is disabled (via setIPv4Enabled:)
* - The socket is explicitly bound to an IPv6 address
* - The socket is connected to an IPv6 address
**/
- (BOOL)isIPv4;
/**
* Returns whether or not this socket is IPv6.
*
* By default this will be true, unless:
* - IPv6 is disabled (via setIPv6Enabled:)
* - The socket is explicitly bound to an IPv4 address
* _ The socket is 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;
#pragma mark Binding
/**
* Binds the UDP socket to the given port.
* 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 may optionally pass a port number of zero to immediately bind the socket,
* yet still allow the OS to automatically assign an available port.
*
* 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 NULL for errPtr.
**/
- (BOOL)bindToPort:(uint16_t)port error:(NSError **)errPtr;
/**
* Binds the UDP socket to the given port and optional interface.
* 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 may optionally pass a port number of zero to immediately bind the socket,
* yet still allow the OS to automatically assign an available port.
*
* The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35").
* You may also use the special strings "localhost" or "loopback" to specify that
* the socket only accept packets from the local machine.
*
* 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 NULL for errPtr.
**/
- (BOOL)bindToPort:(uint16_t)port interface:(nullable NSString *)interface error:(NSError **)errPtr;
/**
* Binds the UDP socket to the given address, specified as a sockaddr structure wrapped in a NSData object.
*
* 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];
*
* 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 NULL for errPtr.
**/
- (BOOL)bindToAddress:(NSData *)localAddr error:(NSError **)errPtr;
#pragma mark Connecting
/**
* 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".
*
* The actual process of 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 it has been connected.
* You can only connect a socket once.
*
* The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2").
*
* This method is asynchronous as it requires a DNS lookup to resolve the given host name.
* If an obvious error is detected, this method immediately returns NO and sets errPtr.
* If you don't care about the error, you can pass nil for errPtr.
* Otherwise, this method returns YES and begins the asynchronous connection process.
* The result of the asynchronous connection process will be reported via the delegate methods.
**/
- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr;
/**
* Connects the UDP socket to the given address, specified as a sockaddr structure wrapped in a NSData object.
*
* 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];
*
* By design, UDP is a connectionless protocol, and connecting is not needed.
*
* Choosing to connect to a specific address has the following effect:
* - You will only be able to send data to the connected address.
* - You will only be able to receive data from the connected address.
* - You will receive ICMP messages that come from the connected address, 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.
*
* Note: Unlike the connectToHost:onPort:error: method, this method does not require a DNS lookup.
* Thus when this method returns, the connection has either failed or fully completed.
* In other words, this method is synchronous, unlike the asynchronous connectToHost::: method.
* However, for compatibility and simplification of delegate code, if this method returns YES
* then the corresponding delegate method (udpSocket:didConnectToHost:port:) is still invoked.
**/
- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr;
#pragma mark Multicast
/**
* Join multicast group.
* Group should be an IP address (eg @"225.228.0.1").
*
* 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)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr;
/**
* Join multicast group.
* Group should be an IP address (eg @"225.228.0.1").
* The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35").
*
* 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)joinMulticastGroup:(NSString *)group onInterface:(nullable NSString *)interface error:(NSError **)errPtr;
- (BOOL)leaveMulticastGroup:(NSString *)group error:(NSError **)errPtr;
- (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(nullable NSString *)interface error:(NSError **)errPtr;
/**
* Send multicast on a specified interface.
* For IPv4, interface should be the the IP address of the interface (eg @"192.168.10.1").
* For IPv6, interface should be the a network interface name (eg @"en0").
*
* 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)sendIPv4MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr;
- (BOOL)sendIPv6MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr;
#pragma mark Reuse Port
/**
* By default, only one socket can be bound to a given IP address + port at a time.
* To enable multiple processes to simultaneously bind to the same address+port,
* you need to enable this functionality in the socket. All processes that wish to
* use the address+port simultaneously must all enable reuse port on the socket
* bound to that port.
**/
- (BOOL)enableReusePort:(BOOL)flag error:(NSError **)errPtr;
#pragma mark Broadcast
/**
* 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 (by the OS) is to prevent
* accidental broadcast messages from flooding the network.
**/
- (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr;
#pragma mark Sending
/**
* Asynchronously sends the given data, with the given timeout and tag.
*
* This method may only be used with a connected socket.
* Recall that connecting is optional for a UDP socket.
* For connected sockets, data can only be sent to the connected address.
* For non-connected sockets, the remote destination is specified for each packet.
* For more information about optionally connecting udp sockets, see the documentation for the connect methods above.
*
* @param data
* The data to send.
* If data is nil or zero-length, this method does nothing.
* If passing NSMutableData, please read the thread-safety notice below.
*
* @param timeout
* The timeout for the send opeartion.
* If the timeout value is negative, the send operation will not use a timeout.
*
* @param tag
* The tag is for your convenience.
* It is not sent or received over the socket in any manner what-so-ever.
* It is reported back as a parameter in the udpSocket:didSendDataWithTag:
* or udpSocket:didNotSendDataWithTag:dueToError: methods.
* You can use it as an array index, state id, type constant, etc.
*
*
* Thread-Safety Note:
* If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while
* the socket is sending it. In other words, it's not safe to alter the data until after the delegate method
* udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying
* that this particular send operation has completed.
* This is due to the fact that GCDAsyncUdpSocket does NOT copy the data.
* It simply retains it for performance reasons.
* Often times, if NSMutableData is passed, it is because a request/response was built up in memory.
* Copying this data adds an unwanted/unneeded overhead.
* If you need to write data from an immutable buffer, and you need to alter the buffer before the socket
* completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time
* when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method.
**/
- (void)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.
* Recall that connecting is optional for a UDP socket.
* For connected sockets, data can only be sent to the connected address.
* For non-connected sockets, the remote destination is specified for each packet.
* For more information about optionally connecting udp sockets, see the documentation for the connect methods above.
*
* @param data
* The data to send.
* If data is nil or zero-length, this method does nothing.
* If passing NSMutableData, please read the thread-safety notice below.
*
* @param host
* The destination to send the udp packet to.
* May be specified as a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2").
* You may also use the convenience strings of "loopback" or "localhost".
*
* @param port
* The port of the host to send to.
*
* @param timeout
* The timeout for the send opeartion.
* If the timeout value is negative, the send operation will not use a timeout.
*
* @param tag
* The tag is for your convenience.
* It is not sent or received over the socket in any manner what-so-ever.
* It is reported back as a parameter in the udpSocket:didSendDataWithTag:
* or udpSocket:didNotSendDataWithTag:dueToError: methods.
* You can use it as an array index, state id, type constant, etc.
*
*
* Thread-Safety Note:
* If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while
* the socket is sending it. In other words, it's not safe to alter the data until after the delegate method
* udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying
* that this particular send operation has completed.
* This is due to the fact that GCDAsyncUdpSocket does NOT copy the data.
* It simply retains it for performance reasons.
* Often times, if NSMutableData is passed, it is because a request/response was built up in memory.
* Copying this data adds an unwanted/unneeded overhead.
* If you need to write data from an immutable buffer, and you need to alter the buffer before the socket
* completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time
* when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method.
**/
- (void)sendData:(NSData *)data
toHost:(NSString *)host
port:(uint16_t)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.
* Recall that connecting is optional for a UDP socket.
* For connected sockets, data can only be sent to the connected address.
* For non-connected sockets, the remote destination is specified for each packet.
* For more information about optionally connecting udp sockets, see the documentation for the connect methods above.
*
* @param data
* The data to send.
* If data is nil or zero-length, this method does nothing.
* If passing NSMutableData, please read the thread-safety notice below.
*
* @param remoteAddr
* The address to send the data to (specified as a sockaddr structure wrapped in a NSData object).
*
* @param timeout
* The timeout for the send opeartion.
* If the timeout value is negative, the send operation will not use a timeout.
*
* @param tag
* The tag is for your convenience.
* It is not sent or received over the socket in any manner what-so-ever.
* It is reported back as a parameter in the udpSocket:didSendDataWithTag:
* or udpSocket:didNotSendDataWithTag:dueToError: methods.
* You can use it as an array index, state id, type constant, etc.
*
*
* Thread-Safety Note:
* If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while
* the socket is sending it. In other words, it's not safe to alter the data until after the delegate method
* udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying
* that this particular send operation has completed.
* This is due to the fact that GCDAsyncUdpSocket does NOT copy the data.
* It simply retains it for performance reasons.
* Often times, if NSMutableData is passed, it is because a request/response was built up in memory.
* Copying this data adds an unwanted/unneeded overhead.
* If you need to write data from an immutable buffer, and you need to alter the buffer before the socket
* completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time
* when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method.
**/
- (void)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag;
/**
* You may optionally set a send filter for the socket.
* A filter can provide several interesting possibilities:
*
* 1. Optional caching of resolved addresses for domain names.
* The cache could later be consulted, resulting in fewer system calls to getaddrinfo.
*
* 2. Reusable modules of code for bandwidth monitoring.
*
* 3. Sometimes traffic shapers are needed to simulate real world environments.
* A filter allows you to write custom code to simulate such environments.
* The ability to code this yourself is especially helpful when your simulated environment
* is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router),
* or the system tools to handle this aren't available (e.g. on a mobile device).
*
* For more information about GCDAsyncUdpSocketSendFilterBlock, see the documentation for its typedef.
* To remove a previously set filter, invoke this method and pass a nil filterBlock and NULL filterQueue.
*
* Note: This method invokes setSendFilter:withQueue:isAsynchronous: (documented below),
* passing YES for the isAsynchronous parameter.
**/
- (void)setSendFilter:(nullable GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(nullable dispatch_queue_t)filterQueue;
/**
* The receive filter can be run via dispatch_async or dispatch_sync.
* Most typical situations call for asynchronous operation.
*
* However, there are a few situations in which synchronous operation is preferred.
* Such is the case when the filter is extremely minimal and fast.
* This is because dispatch_sync is faster than dispatch_async.
*
* If you choose synchronous operation, be aware of possible deadlock conditions.
* Since the socket queue is executing your block via dispatch_sync,
* then you cannot perform any tasks which may invoke dispatch_sync on the socket queue.
* For example, you can't query properties on the socket.
**/
- (void)setSendFilter:(nullable GCDAsyncUdpSocketSendFilterBlock)filterBlock
withQueue:(nullable dispatch_queue_t)filterQueue
isAsynchronous:(BOOL)isAsynchronous;
#pragma mark Receiving
/**
* There are two modes of operation for receiving packets: one-at-a-time & continuous.
*
* In one-at-a-time mode, you call receiveOnce everytime your delegate is ready to process an incoming udp packet.
* Receiving packets one-at-a-time may be better suited for implementing certain state machine code,
* where your state machine may not always be ready to process incoming packets.
*
* In continuous mode, the delegate is invoked immediately everytime incoming udp packets are received.
* Receiving packets continuously is better suited to real-time streaming applications.
*
* You may switch back and forth between one-at-a-time mode and continuous mode.
* If the socket is currently in continuous mode, calling this method will switch it to one-at-a-time mode.
*
* When a packet is received (and not filtered by the optional receive filter),
* the delegate method (udpSocket:didReceiveData:fromAddress:withFilterContext:) is invoked.
*
* If the socket is able to begin receiving packets, this method returns YES.
* Otherwise it returns NO, and sets the errPtr with appropriate error information.
*
* An example error:
* You created a udp socket to act as a server, and immediately called receive.
* You forgot to first bind the socket to a port number, and received a error with a message like:
* "Must bind socket before you can receive data."
**/
- (BOOL)receiveOnce:(NSError **)errPtr;
/**
* There are two modes of operation for receiving packets: one-at-a-time & continuous.
*
* In one-at-a-time mode, you call receiveOnce everytime your delegate is ready to process an incoming udp packet.
* Receiving packets one-at-a-time may be better suited for implementing certain state machine code,
* where your state machine may not always be ready to process incoming packets.
*
* In continuous mode, the delegate is invoked immediately everytime incoming udp packets are received.
* Receiving packets continuously is better suited to real-time streaming applications.
*
* You may switch back and forth between one-at-a-time mode and continuous mode.
* If the socket is currently in one-at-a-time mode, calling this method will switch it to continuous mode.
*
* For every received packet (not filtered by the optional receive filter),
* the delegate method (udpSocket:didReceiveData:fromAddress:withFilterContext:) is invoked.
*
* If the socket is able to begin receiving packets, this method returns YES.
* Otherwise it returns NO, and sets the errPtr with appropriate error information.
*
* An example error:
* You created a udp socket to act as a server, and immediately called receive.
* You forgot to first bind the socket to a port number, and received a error with a message like:
* "Must bind socket before you can receive data."
**/
- (BOOL)beginReceiving:(NSError **)errPtr;
/**
* If the socket is currently receiving (beginReceiving has been called), this method pauses the receiving.
* That is, it won't read any more packets from the underlying OS socket until beginReceiving is called again.
*
* Important Note:
* GCDAsyncUdpSocket may be running in parallel with your code.
* That is, your delegate is likely running on a separate thread/dispatch_queue.
* When you invoke this method, GCDAsyncUdpSocket may have already dispatched delegate methods to be invoked.
* Thus, if those delegate methods have already been dispatch_async'd,
* your didReceive delegate method may still be invoked after this method has been called.
* You should be aware of this, and program defensively.
**/
- (void)pauseReceiving;
/**
* You may optionally set a receive filter for the socket.
* This receive filter may be set to run in its own queue (independent of delegate queue).
*
* A filter can provide several useful features.
*
* 1. Many times udp packets need to be parsed.
* Since the filter can run in its own independent queue, you can parallelize this parsing quite easily.
* The end result is a parallel socket io, datagram parsing, and packet processing.
*
* 2. Many times udp packets are discarded because they are duplicate/unneeded/unsolicited.
* The filter can prevent such packets from arriving at the delegate.
* And because the filter can run in its own independent queue, this doesn't slow down the delegate.
*
* - Since the udp protocol does not guarantee delivery, udp packets may be lost.
* Many protocols built atop udp thus provide various resend/re-request algorithms.
* This sometimes results in duplicate packets arriving.
* A filter may allow you to architect the duplicate detection code to run in parallel to normal processing.
*
* - Since the udp socket may be connectionless, its possible for unsolicited packets to arrive.
* Such packets need to be ignored.
*
* 3. Sometimes traffic shapers are needed to simulate real world environments.
* A filter allows you to write custom code to simulate such environments.
* The ability to code this yourself is especially helpful when your simulated environment
* is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router),
* or the system tools to handle this aren't available (e.g. on a mobile device).
*
* Example:
*
* GCDAsyncUdpSocketReceiveFilterBlock filter = ^BOOL (NSData *data, NSData *address, id *context) {
*
* MyProtocolMessage *msg = [MyProtocol parseMessage:data];
*
* *context = response;
* return (response != nil);
* };
* [udpSocket setReceiveFilter:filter withQueue:myParsingQueue];
*
* For more information about GCDAsyncUdpSocketReceiveFilterBlock, see the documentation for its typedef.
* To remove a previously set filter, invoke this method and pass a nil filterBlock and NULL filterQueue.
*
* Note: This method invokes setReceiveFilter:withQueue:isAsynchronous: (documented below),
* passing YES for the isAsynchronous parameter.
**/
- (void)setReceiveFilter:(nullable GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(nullable dispatch_queue_t)filterQueue;
/**
* The receive filter can be run via dispatch_async or dispatch_sync.
* Most typical situations call for asynchronous operation.
*
* However, there are a few situations in which synchronous operation is preferred.
* Such is the case when the filter is extremely minimal and fast.
* This is because dispatch_sync is faster than dispatch_async.
*
* If you choose synchronous operation, be aware of possible deadlock conditions.
* Since the socket queue is executing your block via dispatch_sync,
* then you cannot perform any tasks which may invoke dispatch_sync on the socket queue.
* For example, you can't query properties on the socket.
**/
- (void)setReceiveFilter:(nullable GCDAsyncUdpSocketReceiveFilterBlock)filterBlock
withQueue:(nullable dispatch_queue_t)filterQueue
isAsynchronous:(BOOL)isAsynchronous;
#pragma mark Closing
/**
* Immediately closes the underlying socket.
* Any pending send operations are discarded.
*
* The GCDAsyncUdpSocket instance may optionally be used again.
* (it will setup/configure/use another unnderlying BSD socket).
**/
- (void)close;
/**
* Closes the underlying socket after all pending send operations have been sent.
*
* The GCDAsyncUdpSocket instance may optionally be used again.
* (it will setup/configure/use another unnderlying BSD socket).
**/
- (void)closeAfterSending;
#pragma mark Advanced
/**
* GCDAsyncSocket maintains thread safety by using an internal serial dispatch_queue.
* In most cases, the instance creates this queue itself.
* However, to allow for maximum flexibility, the internal queue may be passed in the init method.
* This allows for some advanced options such as controlling socket priority via target queues.
* However, when one begins to use target queues like this, they open the door to some specific deadlock issues.
*
* For example, imagine there are 2 queues:
* dispatch_queue_t socketQueue;
* dispatch_queue_t socketTargetQueue;
*
* If you do this (pseudo-code):
* socketQueue.targetQueue = socketTargetQueue;
*
* Then all socketQueue operations will actually get run on the given socketTargetQueue.
* This is fine and works great in most situations.
* But if you run code directly from within the socketTargetQueue that accesses the socket,
* you could potentially get deadlock. Imagine the following code:
*
* - (BOOL)socketHasSomething
* {
* __block BOOL result = NO;
* dispatch_block_t block = ^{
* result = [self someInternalMethodToBeRunOnlyOnSocketQueue];
* }
* if (is_executing_on_queue(socketQueue))
* block();
* else
* dispatch_sync(socketQueue, block);
*
* return result;
* }
*
* What happens if you call this method from the socketTargetQueue? The result is deadlock.
* This is because the GCD API offers no mechanism to discover a queue's targetQueue.
* Thus we have no idea if our socketQueue is configured with a targetQueue.
* If we had this information, we could easily avoid deadlock.
* But, since these API's are missing or unfeasible, you'll have to explicitly set it.
*
* IF you pass a socketQueue via the init method,
* AND you've configured the passed socketQueue with a targetQueue,
* THEN you should pass the end queue in the target hierarchy.
*
* For example, consider the following queue hierarchy:
* socketQueue -> ipQueue -> moduleQueue
*
* This example demonstrates priority shaping within some server.
* All incoming client connections from the same IP address are executed on the same target queue.
* And all connections for a particular module are executed on the same target queue.
* Thus, the priority of all networking for the entire module can be changed on the fly.
* Additionally, networking traffic from a single IP cannot monopolize the module.
*
* Here's how you would accomplish something like that:
* - (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock
* {
* dispatch_queue_t socketQueue = dispatch_queue_create("", NULL);
* dispatch_queue_t ipQueue = [self ipQueueForAddress:address];
*
* dispatch_set_target_queue(socketQueue, ipQueue);
* dispatch_set_target_queue(iqQueue, moduleQueue);
*
* return socketQueue;
* }
* - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
* {
* [clientConnections addObject:newSocket];
* [newSocket markSocketQueueTargetQueue:moduleQueue];
* }
*
* Note: This workaround is ONLY needed if you intend to execute code directly on the ipQueue or moduleQueue.
* This is often NOT the case, as such queues are used solely for execution shaping.
**/
- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreConfiguredTargetQueue;
- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreviouslyConfiguredTargetQueue;
/**
* It's not thread-safe to access certain variables from outside the socket's internal queue.
*
* For example, the socket file descriptor.
* File descriptors are simply integers which reference an index in the per-process file table.
* However, when one requests a new file descriptor (by opening a file or socket),
* the file descriptor returned is guaranteed to be the lowest numbered unused descriptor.
* So if we're not careful, the following could be possible:
*
* - Thread A invokes a method which returns the socket's file descriptor.
* - The socket is closed via the socket's internal queue on thread B.
* - Thread C opens a file, and subsequently receives the file descriptor that was previously the socket's FD.
* - Thread A is now accessing/altering the file instead of the socket.
*
* In addition to this, other variables are not actually objects,
* and thus cannot be retained/released or even autoreleased.
* An example is the sslContext, of type SSLContextRef, which is actually a malloc'd struct.
*
* Although there are internal variables that make it difficult to maintain thread-safety,
* it is important to provide access to these variables
* to ensure this class can be used in a wide array of environments.
* This method helps to accomplish this by invoking the current block on the socket's internal queue.
* The methods below can be invoked from within the block to access
* those generally thread-unsafe internal variables in a thread-safe manner.
* The given block will be invoked synchronously on the socket's internal queue.
*
* If you save references to any protected variables and use them outside the block, you do so at your own peril.
**/
- (void)performBlock:(dispatch_block_t)block;
/**
* These methods are only available from within the context of a performBlock: invocation.
* See the documentation for the performBlock: method above.
*
* Provides access to the socket's file descriptor(s).
* If the socket isn't connected, or explicity bound to a particular interface,
* it might actually have multiple internal socket file descriptors - one for IPv4 and one for IPv6.
**/
- (int)socketFD;
- (int)socket4FD;
- (int)socket6FD;
#if TARGET_OS_IPHONE
/**
* These methods are only available from within the context of a performBlock: invocation.
* See the documentation for the performBlock: method above.
*
* Returns (creating if necessary) a CFReadStream/CFWriteStream for the internal socket.
*
* Generally GCDAsyncUdpSocket doesn't use CFStream. (It uses the faster GCD API's.)
* However, if you need one for any reason,
* these methods are a convenient way to get access to a safe instance of one.
**/
- (nullable CFReadStreamRef)readStream;
- (nullable CFWriteStreamRef)writeStream;
/**
* This method is only available from within the context of a performBlock: invocation.
* See the documentation for the performBlock: method above.
*
* Configures the socket to allow it to operate when the iOS application has been backgrounded.
* In other words, this method creates a read & write stream, and invokes:
*
* CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
* CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
*
* Returns YES if successful, NO otherwise.
*
* Example usage:
*
* [asyncUdpSocket performBlock:^{
* [asyncUdpSocket enableBackgroundingOnSocket];
* }];
*
*
* NOTE : Apple doesn't currently support backgrounding UDP sockets. (Only TCP for now).
**/
//- (BOOL)enableBackgroundingOnSockets;
#endif
#pragma mark Utilities
/**
* Extracting host/port/family information from raw address data.
**/
+ (nullable NSString *)hostFromAddress:(NSData *)address;
+ (uint16_t)portFromAddress:(NSData *)address;
+ (int)familyFromAddress:(NSData *)address;
+ (BOOL)isIPv4Address:(NSData *)address;
+ (BOOL)isIPv6Address:(NSData *)address;
+ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(uint16_t * __nullable)portPtr fromAddress:(NSData *)address;
+ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(uint16_t * __nullable)portPtr family:(int * __nullable)afPtr fromAddress:(NSData *)address;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncUdpSocket.m
================================================
//
// GCDAsyncUdpSocket
//
// This class is in the public domain.
// Originally created by Robbie Hanson of Deusty LLC.
// Updated and maintained by Deusty LLC and the Apple development community.
//
// https://github.com/robbiehanson/CocoaAsyncSocket
//
#import "GCDAsyncUdpSocket.h"
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
// For more information see: https://github.com/robbiehanson/CocoaAsyncSocket/wiki/ARC
#endif
#if TARGET_OS_IPHONE
#import
#import
#endif
#import
#import
#import
#import
#import
#import
#import
#if 0
// Logging Enabled - See log level below
// Logging uses the CocoaLumberjack framework (which is also GCD based).
// https://github.com/robbiehanson/CocoaLumberjack
//
// It allows us to do a lot of logging without significantly slowing down the code.
#import "DDLog.h"
#define LogAsync NO
#define LogContext 65535
#define LogObjc(flg, frmt, ...) LOG_OBJC_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__)
#define LogC(flg, frmt, ...) LOG_C_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__)
#define LogError(frmt, ...) LogObjc(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogWarn(frmt, ...) LogObjc(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogInfo(frmt, ...) LogObjc(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogVerbose(frmt, ...) LogObjc(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogCError(frmt, ...) LogC(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogCWarn(frmt, ...) LogC(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogCInfo(frmt, ...) LogC(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogCVerbose(frmt, ...) LogC(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogTrace() LogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD)
#define LogCTrace() LogC(LOG_FLAG_VERBOSE, @"%@: %s", THIS_FILE, __FUNCTION__)
// Log levels : off, error, warn, info, verbose
static const int logLevel = LOG_LEVEL_VERBOSE;
#else
// Logging Disabled
#define LogError(frmt, ...) {}
#define LogWarn(frmt, ...) {}
#define LogInfo(frmt, ...) {}
#define LogVerbose(frmt, ...) {}
#define LogCError(frmt, ...) {}
#define LogCWarn(frmt, ...) {}
#define LogCInfo(frmt, ...) {}
#define LogCVerbose(frmt, ...) {}
#define LogTrace() {}
#define LogCTrace(frmt, ...) {}
#endif
/**
* Seeing a return statements within an inner block
* can sometimes be mistaken for a return point of the enclosing method.
* This makes inline blocks a bit easier to read.
**/
#define return_from_block return
/**
* A socket file descriptor is really just an integer.
* It represents the index of the socket within the kernel.
* This makes invalid file descriptor comparisons easier to read.
**/
#define SOCKET_NULL -1
/**
* Just to type less code.
**/
#define AutoreleasedBlock(block) ^{ @autoreleasepool { block(); }}
@class GCDAsyncUdpSendPacket;
NSString *const GCDAsyncUdpSocketException = @"GCDAsyncUdpSocketException";
NSString *const GCDAsyncUdpSocketErrorDomain = @"GCDAsyncUdpSocketErrorDomain";
NSString *const GCDAsyncUdpSocketQueueName = @"GCDAsyncUdpSocket";
NSString *const GCDAsyncUdpSocketThreadName = @"GCDAsyncUdpSocket-CFStream";
enum GCDAsyncUdpSocketFlags
{
kDidCreateSockets = 1 << 0, // If set, the sockets have been created.
kDidBind = 1 << 1, // If set, bind has been called.
kConnecting = 1 << 2, // If set, a connection attempt is in progress.
kDidConnect = 1 << 3, // If set, socket is connected.
kReceiveOnce = 1 << 4, // If set, one-at-a-time receive is enabled
kReceiveContinuous = 1 << 5, // If set, continuous receive is enabled
kIPv4Deactivated = 1 << 6, // If set, socket4 was closed due to bind or connect on IPv6.
kIPv6Deactivated = 1 << 7, // If set, socket6 was closed due to bind or connect on IPv4.
kSend4SourceSuspended = 1 << 8, // If set, send4Source is suspended.
kSend6SourceSuspended = 1 << 9, // If set, send6Source is suspended.
kReceive4SourceSuspended = 1 << 10, // If set, receive4Source is suspended.
kReceive6SourceSuspended = 1 << 11, // If set, receive6Source is suspended.
kSock4CanAcceptBytes = 1 << 12, // If set, we know socket4 can accept bytes. If unset, it's unknown.
kSock6CanAcceptBytes = 1 << 13, // If set, we know socket6 can accept bytes. If unset, it's unknown.
kForbidSendReceive = 1 << 14, // If set, no new send or receive operations are allowed to be queued.
kCloseAfterSends = 1 << 15, // If set, close as soon as no more sends are queued.
kFlipFlop = 1 << 16, // Used to alternate between IPv4 and IPv6 sockets.
#if TARGET_OS_IPHONE
kAddedStreamListener = 1 << 17, // If set, CFStreams have been added to listener thread
#endif
};
enum GCDAsyncUdpSocketConfig
{
kIPv4Disabled = 1 << 0, // If set, IPv4 is disabled
kIPv6Disabled = 1 << 1, // If set, IPv6 is disabled
kPreferIPv4 = 1 << 2, // If set, IPv4 is preferred over IPv6
kPreferIPv6 = 1 << 3, // If set, IPv6 is preferred over IPv4
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface GCDAsyncUdpSocket ()
{
#if __has_feature(objc_arc_weak)
__weak id delegate;
#else
__unsafe_unretained id delegate;
#endif
dispatch_queue_t delegateQueue;
GCDAsyncUdpSocketReceiveFilterBlock receiveFilterBlock;
dispatch_queue_t receiveFilterQueue;
BOOL receiveFilterAsync;
GCDAsyncUdpSocketSendFilterBlock sendFilterBlock;
dispatch_queue_t sendFilterQueue;
BOOL sendFilterAsync;
uint32_t flags;
uint16_t config;
uint16_t max4ReceiveSize;
uint32_t max6ReceiveSize;
uint16_t maxSendSize;
int socket4FD;
int socket6FD;
dispatch_queue_t socketQueue;
dispatch_source_t send4Source;
dispatch_source_t send6Source;
dispatch_source_t receive4Source;
dispatch_source_t receive6Source;
dispatch_source_t sendTimer;
GCDAsyncUdpSendPacket *currentSend;
NSMutableArray *sendQueue;
unsigned long socket4FDBytesAvailable;
unsigned long socket6FDBytesAvailable;
uint32_t pendingFilterOperations;
NSData *cachedLocalAddress4;
NSString *cachedLocalHost4;
uint16_t cachedLocalPort4;
NSData *cachedLocalAddress6;
NSString *cachedLocalHost6;
uint16_t cachedLocalPort6;
NSData *cachedConnectedAddress;
NSString *cachedConnectedHost;
uint16_t cachedConnectedPort;
int cachedConnectedFamily;
void *IsOnSocketQueueOrTargetQueueKey;
#if TARGET_OS_IPHONE
CFStreamClientContext streamContext;
CFReadStreamRef readStream4;
CFReadStreamRef readStream6;
CFWriteStreamRef writeStream4;
CFWriteStreamRef writeStream6;
#endif
id userData;
}
- (void)resumeSend4Source;
- (void)resumeSend6Source;
- (void)resumeReceive4Source;
- (void)resumeReceive6Source;
- (void)closeSockets;
- (void)maybeConnect;
- (BOOL)connectWithAddress4:(NSData *)address4 error:(NSError **)errPtr;
- (BOOL)connectWithAddress6:(NSData *)address6 error:(NSError **)errPtr;
- (void)maybeDequeueSend;
- (void)doPreSend;
- (void)doSend;
- (void)endCurrentSend;
- (void)setupSendTimerWithTimeout:(NSTimeInterval)timeout;
- (void)doReceive;
- (void)doReceiveEOF;
- (void)closeWithError:(NSError *)error;
- (BOOL)performMulticastRequest:(int)requestType forGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr;
#if TARGET_OS_IPHONE
- (BOOL)createReadAndWriteStreams:(NSError **)errPtr;
- (BOOL)registerForStreamCallbacks:(NSError **)errPtr;
- (BOOL)addStreamsToRunLoop:(NSError **)errPtr;
- (BOOL)openStreams:(NSError **)errPtr;
- (void)removeStreamsFromRunLoop;
- (void)closeReadAndWriteStreams;
#endif
+ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4;
+ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6;
+ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4;
+ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6;
#if TARGET_OS_IPHONE
// Forward declaration
+ (void)listenerThread:(id)unused;
#endif
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* The GCDAsyncUdpSendPacket encompasses the instructions for a single send/write.
**/
@interface GCDAsyncUdpSendPacket : NSObject {
@public
NSData *buffer;
NSTimeInterval timeout;
long tag;
BOOL resolveInProgress;
BOOL filterInProgress;
NSArray *resolvedAddresses;
NSError *resolveError;
NSData *address;
int addressFamily;
}
- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i NS_DESIGNATED_INITIALIZER;
@end
@implementation GCDAsyncUdpSendPacket
// Cover the superclass' designated initializer
- (instancetype)init NS_UNAVAILABLE
{
NSAssert(0, @"Use the designated initializer");
return nil;
}
- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i
{
if ((self = [super init]))
{
buffer = d;
timeout = t;
tag = i;
resolveInProgress = NO;
}
return self;
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface GCDAsyncUdpSpecialPacket : NSObject {
@public
// uint8_t type;
BOOL resolveInProgress;
NSArray *addresses;
NSError *error;
}
- (instancetype)init NS_DESIGNATED_INITIALIZER;
@end
@implementation GCDAsyncUdpSpecialPacket
- (instancetype)init
{
self = [super init];
return self;
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation GCDAsyncUdpSocket
- (instancetype)init
{
LogTrace();
return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL];
}
- (instancetype)initWithSocketQueue:(dispatch_queue_t)sq
{
LogTrace();
return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq];
}
- (instancetype)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq
{
LogTrace();
return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL];
}
- (instancetype)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq
{
LogTrace();
if ((self = [super init]))
{
delegate = aDelegate;
if (dq)
{
delegateQueue = dq;
#if !OS_OBJECT_USE_OBJC
dispatch_retain(delegateQueue);
#endif
}
max4ReceiveSize = 65535;
max6ReceiveSize = 65535;
maxSendSize = 65535;
socket4FD = SOCKET_NULL;
socket6FD = SOCKET_NULL;
if (sq)
{
NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
@"The given socketQueue parameter must not be a concurrent queue.");
NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
@"The given socketQueue parameter must not be a concurrent queue.");
NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
@"The given socketQueue parameter must not be a concurrent queue.");
socketQueue = sq;
#if !OS_OBJECT_USE_OBJC
dispatch_retain(socketQueue);
#endif
}
else
{
socketQueue = dispatch_queue_create([GCDAsyncUdpSocketQueueName UTF8String], NULL);
}
// The dispatch_queue_set_specific() and dispatch_get_specific() functions take a "void *key" parameter.
// From the documentation:
//
// > Keys are only compared as pointers and are never dereferenced.
// > Thus, you can use a pointer to a static variable for a specific subsystem or
// > any other value that allows you to identify the value uniquely.
//
// We're just going to use the memory address of an ivar.
// Specifically an ivar that is explicitly named for our purpose to make the code more readable.
//
// However, it feels tedious (and less readable) to include the "&" all the time:
// dispatch_get_specific(&IsOnSocketQueueOrTargetQueueKey)
//
// So we're going to make it so it doesn't matter if we use the '&' or not,
// by assigning the value of the ivar to the address of the ivar.
// Thus: IsOnSocketQueueOrTargetQueueKey == &IsOnSocketQueueOrTargetQueueKey;
IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey;
void *nonNullUnusedPointer = (__bridge void *)self;
dispatch_queue_set_specific(socketQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL);
currentSend = nil;
sendQueue = [[NSMutableArray alloc] initWithCapacity:5];
#if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillEnterForeground:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
#endif
}
return self;
}
- (void)dealloc
{
LogInfo(@"%@ - %@ (start)", THIS_METHOD, self);
#if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] removeObserver:self];
#endif
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
[self closeWithError:nil];
}
else
{
dispatch_sync(socketQueue, ^{
[self closeWithError:nil];
});
}
delegate = nil;
#if !OS_OBJECT_USE_OBJC
if (delegateQueue) dispatch_release(delegateQueue);
#endif
delegateQueue = NULL;
#if !OS_OBJECT_USE_OBJC
if (socketQueue) dispatch_release(socketQueue);
#endif
socketQueue = NULL;
LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Configuration
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (id)delegate
{
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
return delegate;
}
else
{
__block id result = nil;
dispatch_sync(socketQueue, ^{
result = self->delegate;
});
return result;
}
}
- (void)setDelegate:(id)newDelegate synchronously:(BOOL)synchronously
{
dispatch_block_t block = ^{
self->delegate = newDelegate;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
block();
}
else {
if (synchronously)
dispatch_sync(socketQueue, block);
else
dispatch_async(socketQueue, block);
}
}
- (void)setDelegate:(id)newDelegate
{
[self setDelegate:newDelegate synchronously:NO];
}
- (void)synchronouslySetDelegate:(id)newDelegate
{
[self setDelegate:newDelegate synchronously:YES];
}
- (dispatch_queue_t)delegateQueue
{
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
return delegateQueue;
}
else
{
__block dispatch_queue_t result = NULL;
dispatch_sync(socketQueue, ^{
result = self->delegateQueue;
});
return result;
}
}
- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously
{
dispatch_block_t block = ^{
#if !OS_OBJECT_USE_OBJC
if (self->delegateQueue) dispatch_release(self->delegateQueue);
if (newDelegateQueue) dispatch_retain(newDelegateQueue);
#endif
self->delegateQueue = newDelegateQueue;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
block();
}
else {
if (synchronously)
dispatch_sync(socketQueue, block);
else
dispatch_async(socketQueue, block);
}
}
- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue
{
[self setDelegateQueue:newDelegateQueue synchronously:NO];
}
- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)newDelegateQueue
{
[self setDelegateQueue:newDelegateQueue synchronously:YES];
}
- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr
{
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
if (delegatePtr) *delegatePtr = delegate;
if (delegateQueuePtr) *delegateQueuePtr = delegateQueue;
}
else
{
__block id dPtr = NULL;
__block dispatch_queue_t dqPtr = NULL;
dispatch_sync(socketQueue, ^{
dPtr = self->delegate;
dqPtr = self->delegateQueue;
});
if (delegatePtr) *delegatePtr = dPtr;
if (delegateQueuePtr) *delegateQueuePtr = dqPtr;
}
}
- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously
{
dispatch_block_t block = ^{
self->delegate = newDelegate;
#if !OS_OBJECT_USE_OBJC
if (self->delegateQueue) dispatch_release(self->delegateQueue);
if (newDelegateQueue) dispatch_retain(newDelegateQueue);
#endif
self->delegateQueue = newDelegateQueue;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
block();
}
else {
if (synchronously)
dispatch_sync(socketQueue, block);
else
dispatch_async(socketQueue, block);
}
}
- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue
{
[self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:NO];
}
- (void)synchronouslySetDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue
{
[self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:YES];
}
- (BOOL)isIPv4Enabled
{
// Note: YES means kIPv4Disabled is OFF
__block BOOL result = NO;
dispatch_block_t block = ^{
result = ((self->config & kIPv4Disabled) == 0);
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (void)setIPv4Enabled:(BOOL)flag
{
// Note: YES means kIPv4Disabled is OFF
dispatch_block_t block = ^{
LogVerbose(@"%@ %@", THIS_METHOD, (flag ? @"YES" : @"NO"));
if (flag)
self->config &= ~kIPv4Disabled;
else
self->config |= kIPv4Disabled;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
- (BOOL)isIPv6Enabled
{
// Note: YES means kIPv6Disabled is OFF
__block BOOL result = NO;
dispatch_block_t block = ^{
result = ((self->config & kIPv6Disabled) == 0);
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (void)setIPv6Enabled:(BOOL)flag
{
// Note: YES means kIPv6Disabled is OFF
dispatch_block_t block = ^{
LogVerbose(@"%@ %@", THIS_METHOD, (flag ? @"YES" : @"NO"));
if (flag)
self->config &= ~kIPv6Disabled;
else
self->config |= kIPv6Disabled;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
- (BOOL)isIPv4Preferred
{
__block BOOL result = NO;
dispatch_block_t block = ^{
result = (self->config & kPreferIPv4) ? YES : NO;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (BOOL)isIPv6Preferred
{
__block BOOL result = NO;
dispatch_block_t block = ^{
result = (self->config & kPreferIPv6) ? YES : NO;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (BOOL)isIPVersionNeutral
{
__block BOOL result = NO;
dispatch_block_t block = ^{
result = (self->config & (kPreferIPv4 | kPreferIPv6)) == 0;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (void)setPreferIPv4
{
dispatch_block_t block = ^{
LogTrace();
self->config |= kPreferIPv4;
self->config &= ~kPreferIPv6;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
- (void)setPreferIPv6
{
dispatch_block_t block = ^{
LogTrace();
self->config &= ~kPreferIPv4;
self->config |= kPreferIPv6;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
- (void)setIPVersionNeutral
{
dispatch_block_t block = ^{
LogTrace();
self->config &= ~kPreferIPv4;
self->config &= ~kPreferIPv6;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
- (uint16_t)maxReceiveIPv4BufferSize
{
__block uint16_t result = 0;
dispatch_block_t block = ^{
result = self->max4ReceiveSize;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (void)setMaxReceiveIPv4BufferSize:(uint16_t)max
{
dispatch_block_t block = ^{
LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max);
self->max4ReceiveSize = max;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
- (uint32_t)maxReceiveIPv6BufferSize
{
__block uint32_t result = 0;
dispatch_block_t block = ^{
result = self->max6ReceiveSize;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (void)setMaxReceiveIPv6BufferSize:(uint32_t)max
{
dispatch_block_t block = ^{
LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max);
self->max6ReceiveSize = max;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
- (void)setMaxSendBufferSize:(uint16_t)max
{
dispatch_block_t block = ^{
LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max);
self->maxSendSize = max;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
- (uint16_t)maxSendBufferSize
{
__block uint16_t result = 0;
dispatch_block_t block = ^{
result = self->maxSendSize;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (id)userData
{
__block id result = nil;
dispatch_block_t block = ^{
result = self->userData;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (void)setUserData:(id)arbitraryUserData
{
dispatch_block_t block = ^{
if (self->userData != arbitraryUserData)
{
self->userData = arbitraryUserData;
}
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Delegate Helpers
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)notifyDidConnectToAddress:(NSData *)anAddress
{
LogTrace();
__strong id theDelegate = delegate;
if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didConnectToAddress:)])
{
NSData *address = [anAddress copy]; // In case param is NSMutableData
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate udpSocket:self didConnectToAddress:address];
}});
}
}
- (void)notifyDidNotConnect:(NSError *)error
{
LogTrace();
__strong id theDelegate = delegate;
if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotConnect:)])
{
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate udpSocket:self didNotConnect:error];
}});
}
}
- (void)notifyDidSendDataWithTag:(long)tag
{
LogTrace();
__strong id theDelegate = delegate;
if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didSendDataWithTag:)])
{
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate udpSocket:self didSendDataWithTag:tag];
}});
}
}
- (void)notifyDidNotSendDataWithTag:(long)tag dueToError:(NSError *)error
{
LogTrace();
__strong id theDelegate = delegate;
if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotSendDataWithTag:dueToError:)])
{
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate udpSocket:self didNotSendDataWithTag:tag dueToError:error];
}});
}
}
- (void)notifyDidReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)context
{
LogTrace();
SEL selector = @selector(udpSocket:didReceiveData:fromAddress:withFilterContext:);
__strong id theDelegate = delegate;
if (delegateQueue && [theDelegate respondsToSelector:selector])
{
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate udpSocket:self didReceiveData:data fromAddress:address withFilterContext:context];
}});
}
}
- (void)notifyDidCloseWithError:(NSError *)error
{
LogTrace();
__strong id theDelegate = delegate;
if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocketDidClose:withError:)])
{
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate udpSocketDidClose:self withError:error];
}});
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Errors
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (NSError *)badConfigError:(NSString *)errMsg
{
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain
code:GCDAsyncUdpSocketBadConfigError
userInfo:userInfo];
}
- (NSError *)badParamError:(NSString *)errMsg
{
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain
code:GCDAsyncUdpSocketBadParamError
userInfo:userInfo];
}
- (NSError *)gaiError:(int)gai_error
{
NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding];
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo];
}
- (NSError *)errnoErrorWithReason:(NSString *)reason
{
NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)];
NSDictionary *userInfo;
if (reason)
userInfo = @{NSLocalizedDescriptionKey : errMsg,
NSLocalizedFailureReasonErrorKey : reason};
else
userInfo = @{NSLocalizedDescriptionKey : errMsg};
return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo];
}
- (NSError *)errnoError
{
return [self errnoErrorWithReason:nil];
}
/**
* Returns a standard send timeout error.
**/
- (NSError *)sendTimeoutError
{
NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncUdpSocketSendTimeoutError",
@"GCDAsyncUdpSocket", [NSBundle mainBundle],
@"Send operation timed out", nil);
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain
code:GCDAsyncUdpSocketSendTimeoutError
userInfo:userInfo];
}
- (NSError *)socketClosedError
{
NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncUdpSocketClosedError",
@"GCDAsyncUdpSocket", [NSBundle mainBundle],
@"Socket closed", nil);
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain code:GCDAsyncUdpSocketClosedError userInfo:userInfo];
}
- (NSError *)otherError:(NSString *)errMsg
{
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain
code:GCDAsyncUdpSocketOtherError
userInfo:userInfo];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Utilities
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)preOp:(NSError **)errPtr
{
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
if (delegate == nil) // Must have delegate set
{
if (errPtr)
{
NSString *msg = @"Attempting to use socket without a delegate. Set a delegate first.";
*errPtr = [self badConfigError:msg];
}
return NO;
}
if (delegateQueue == NULL) // Must have delegate queue set
{
if (errPtr)
{
NSString *msg = @"Attempting to use socket without a delegate queue. Set a delegate queue first.";
*errPtr = [self badConfigError:msg];
}
return NO;
}
return YES;
}
/**
* This method executes on a global concurrent queue.
* When complete, it executes the given completion block on the socketQueue.
**/
- (void)asyncResolveHost:(NSString *)aHost
port:(uint16_t)port
withCompletionBlock:(void (^)(NSArray *addresses, NSError *error))completionBlock
{
LogTrace();
// Check parameter(s)
if (aHost == nil)
{
NSString *msg = @"The host param is nil. Should be domain name or IP address string.";
NSError *error = [self badParamError:msg];
// We should still use dispatch_async since this method is expected to be asynchronous
dispatch_async(socketQueue, ^{ @autoreleasepool {
completionBlock(nil, error);
}});
return;
}
// It's possible that the given aHost parameter is actually a NSMutableString.
// So we want to copy it now, within this block that will be executed synchronously.
// This way the asynchronous lookup block below doesn't have to worry about it changing.
NSString *host = [aHost copy];
dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool {
NSMutableArray *addresses = [NSMutableArray arrayWithCapacity:2];
NSError *error = nil;
if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"])
{
// Use LOOPBACK address
struct sockaddr_in sockaddr4;
memset(&sockaddr4, 0, sizeof(sockaddr4));
sockaddr4.sin_len = sizeof(struct sockaddr_in);
sockaddr4.sin_family = AF_INET;
sockaddr4.sin_port = htons(port);
sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
struct sockaddr_in6 sockaddr6;
memset(&sockaddr6, 0, sizeof(sockaddr6));
sockaddr6.sin6_len = sizeof(struct sockaddr_in6);
sockaddr6.sin6_family = AF_INET6;
sockaddr6.sin6_port = htons(port);
sockaddr6.sin6_addr = in6addr_loopback;
// Wrap the native address structures and add to list
[addresses addObject:[NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]];
[addresses addObject:[NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]];
}
else
{
NSString *portStr = [NSString stringWithFormat:@"%hu", port];
struct addrinfo hints, *res, *res0;
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0);
if (gai_error)
{
error = [self gaiError:gai_error];
}
else
{
for(res = res0; res; res = res->ai_next)
{
if (res->ai_family == AF_INET)
{
// Found IPv4 address
// Wrap the native address structure and add to list
[addresses addObject:[NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]];
}
else if (res->ai_family == AF_INET6)
{
// Fixes connection issues with IPv6, it is the same solution for udp socket.
// https://github.com/robbiehanson/CocoaAsyncSocket/issues/429#issuecomment-222477158
struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)(void *)res->ai_addr;
in_port_t *portPtr = &sockaddr->sin6_port;
if ((portPtr != NULL) && (*portPtr == 0)) {
*portPtr = htons(port);
}
// Found IPv6 address
// Wrap the native address structure and add to list
[addresses addObject:[NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]];
}
}
freeaddrinfo(res0);
if ([addresses count] == 0)
{
error = [self gaiError:EAI_FAIL];
}
}
}
dispatch_async(self->socketQueue, ^{ @autoreleasepool {
completionBlock(addresses, error);
}});
}});
}
/**
* This method picks an address from the given list of addresses.
* The address picked depends upon which protocols are disabled, deactived, & preferred.
*
* Returns the address family (AF_INET or AF_INET6) of the picked address,
* or AF_UNSPEC and the corresponding error is there's a problem.
**/
- (int)getAddress:(NSData **)addressPtr error:(NSError **)errorPtr fromAddresses:(NSArray *)addresses
{
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
NSAssert([addresses count] > 0, @"Expected at least one address");
int resultAF = AF_UNSPEC;
NSData *resultAddress = nil;
NSError *resultError = nil;
// Check for problems
BOOL resolvedIPv4Address = NO;
BOOL resolvedIPv6Address = NO;
for (NSData *address in addresses)
{
switch ([[self class] familyFromAddress:address])
{
case AF_INET : resolvedIPv4Address = YES; break;
case AF_INET6 : resolvedIPv6Address = YES; break;
default : NSAssert(NO, @"Addresses array contains invalid address");
}
}
BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
if (isIPv4Disabled && !resolvedIPv6Address)
{
NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address(es).";
resultError = [self otherError:msg];
if (addressPtr) *addressPtr = resultAddress;
if (errorPtr) *errorPtr = resultError;
return resultAF;
}
if (isIPv6Disabled && !resolvedIPv4Address)
{
NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address(es).";
resultError = [self otherError:msg];
if (addressPtr) *addressPtr = resultAddress;
if (errorPtr) *errorPtr = resultError;
return resultAF;
}
BOOL isIPv4Deactivated = (flags & kIPv4Deactivated) ? YES : NO;
BOOL isIPv6Deactivated = (flags & kIPv6Deactivated) ? YES : NO;
if (isIPv4Deactivated && !resolvedIPv6Address)
{
NSString *msg = @"IPv4 has been deactivated due to bind/connect, and DNS lookup found no IPv6 address(es).";
resultError = [self otherError:msg];
if (addressPtr) *addressPtr = resultAddress;
if (errorPtr) *errorPtr = resultError;
return resultAF;
}
if (isIPv6Deactivated && !resolvedIPv4Address)
{
NSString *msg = @"IPv6 has been deactivated due to bind/connect, and DNS lookup found no IPv4 address(es).";
resultError = [self otherError:msg];
if (addressPtr) *addressPtr = resultAddress;
if (errorPtr) *errorPtr = resultError;
return resultAF;
}
// Extract first IPv4 and IPv6 address in list
BOOL ipv4WasFirstInList = YES;
NSData *address4 = nil;
NSData *address6 = nil;
for (NSData *address in addresses)
{
int af = [[self class] familyFromAddress:address];
if (af == AF_INET)
{
if (address4 == nil)
{
address4 = address;
if (address6)
break;
else
ipv4WasFirstInList = YES;
}
}
else // af == AF_INET6
{
if (address6 == nil)
{
address6 = address;
if (address4)
break;
else
ipv4WasFirstInList = NO;
}
}
}
// Determine socket type
BOOL preferIPv4 = (config & kPreferIPv4) ? YES : NO;
BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO;
BOOL useIPv4 = ((preferIPv4 && address4) || (address6 == nil));
BOOL useIPv6 = ((preferIPv6 && address6) || (address4 == nil));
NSAssert(!(preferIPv4 && preferIPv6), @"Invalid config state");
NSAssert(!(useIPv4 && useIPv6), @"Invalid logic");
if (useIPv4 || (!useIPv6 && ipv4WasFirstInList))
{
resultAF = AF_INET;
resultAddress = address4;
}
else
{
resultAF = AF_INET6;
resultAddress = address6;
}
if (addressPtr) *addressPtr = resultAddress;
if (errorPtr) *errorPtr = resultError;
return resultAF;
}
/**
* Finds the address(es) of an interface description.
* An inteface description may be an interface name (en0, en1, lo0) or corresponding IP (192.168.4.34).
**/
- (void)convertIntefaceDescription:(NSString *)interfaceDescription
port:(uint16_t)port
intoAddress4:(NSData **)interfaceAddr4Ptr
address6:(NSData **)interfaceAddr6Ptr
{
NSData *addr4 = nil;
NSData *addr6 = nil;
if (interfaceDescription == nil)
{
// ANY address
struct sockaddr_in sockaddr4;
memset(&sockaddr4, 0, sizeof(sockaddr4));
sockaddr4.sin_len = sizeof(sockaddr4);
sockaddr4.sin_family = AF_INET;
sockaddr4.sin_port = htons(port);
sockaddr4.sin_addr.s_addr = htonl(INADDR_ANY);
struct sockaddr_in6 sockaddr6;
memset(&sockaddr6, 0, sizeof(sockaddr6));
sockaddr6.sin6_len = sizeof(sockaddr6);
sockaddr6.sin6_family = AF_INET6;
sockaddr6.sin6_port = htons(port);
sockaddr6.sin6_addr = in6addr_any;
addr4 = [NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)];
addr6 = [NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)];
}
else if ([interfaceDescription isEqualToString:@"localhost"] ||
[interfaceDescription isEqualToString:@"loopback"])
{
// LOOPBACK address
struct sockaddr_in sockaddr4;
memset(&sockaddr4, 0, sizeof(sockaddr4));
sockaddr4.sin_len = sizeof(struct sockaddr_in);
sockaddr4.sin_family = AF_INET;
sockaddr4.sin_port = htons(port);
sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
struct sockaddr_in6 sockaddr6;
memset(&sockaddr6, 0, sizeof(sockaddr6));
sockaddr6.sin6_len = sizeof(struct sockaddr_in6);
sockaddr6.sin6_family = AF_INET6;
sockaddr6.sin6_port = htons(port);
sockaddr6.sin6_addr = in6addr_loopback;
addr4 = [NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)];
addr6 = [NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)];
}
else
{
const char *iface = [interfaceDescription UTF8String];
struct ifaddrs *addrs;
const struct ifaddrs *cursor;
if ((getifaddrs(&addrs) == 0))
{
cursor = addrs;
while (cursor != NULL)
{
if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET))
{
// IPv4
struct sockaddr_in *addr = (struct sockaddr_in *)(void *)cursor->ifa_addr;
if (strcmp(cursor->ifa_name, iface) == 0)
{
// Name match
struct sockaddr_in nativeAddr4 = *addr;
nativeAddr4.sin_port = htons(port);
addr4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
}
else
{
char ip[INET_ADDRSTRLEN];
const char *conversion;
conversion = inet_ntop(AF_INET, &addr->sin_addr, ip, sizeof(ip));
if ((conversion != NULL) && (strcmp(ip, iface) == 0))
{
// IP match
struct sockaddr_in nativeAddr4 = *addr;
nativeAddr4.sin_port = htons(port);
addr4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
}
}
}
else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6))
{
// IPv6
const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)(const void *)cursor->ifa_addr;
if (strcmp(cursor->ifa_name, iface) == 0)
{
// Name match
struct sockaddr_in6 nativeAddr6 = *addr;
nativeAddr6.sin6_port = htons(port);
addr6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
}
else
{
char ip[INET6_ADDRSTRLEN];
const char *conversion;
conversion = inet_ntop(AF_INET6, &addr->sin6_addr, ip, sizeof(ip));
if ((conversion != NULL) && (strcmp(ip, iface) == 0))
{
// IP match
struct sockaddr_in6 nativeAddr6 = *addr;
nativeAddr6.sin6_port = htons(port);
addr6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
}
}
}
cursor = cursor->ifa_next;
}
freeifaddrs(addrs);
}
}
if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4;
if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6;
}
/**
* Converts a numeric hostname into its corresponding address.
* The hostname is expected to be an IPv4 or IPv6 address represented as a human-readable string. (e.g. 192.168.4.34)
**/
- (void)convertNumericHost:(NSString *)numericHost
port:(uint16_t)port
intoAddress4:(NSData **)addr4Ptr
address6:(NSData **)addr6Ptr
{
NSData *addr4 = nil;
NSData *addr6 = nil;
if (numericHost)
{
NSString *portStr = [NSString stringWithFormat:@"%hu", port];
struct addrinfo hints, *res, *res0;
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_flags = AI_NUMERICHOST; // No name resolution should be attempted
if (getaddrinfo([numericHost UTF8String], [portStr UTF8String], &hints, &res0) == 0)
{
for (res = res0; res; res = res->ai_next)
{
if ((addr4 == nil) && (res->ai_family == AF_INET))
{
// Found IPv4 address
// Wrap the native address structure
addr4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
}
else if ((addr6 == nil) && (res->ai_family == AF_INET6))
{
// Found IPv6 address
// Wrap the native address structure
addr6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
}
}
freeaddrinfo(res0);
}
}
if (addr4Ptr) *addr4Ptr = addr4;
if (addr6Ptr) *addr6Ptr = addr6;
}
- (BOOL)isConnectedToAddress4:(NSData *)someAddr4
{
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
NSAssert(flags & kDidConnect, @"Not connected");
NSAssert(cachedConnectedAddress, @"Expected cached connected address");
if (cachedConnectedFamily != AF_INET)
{
return NO;
}
const struct sockaddr_in *sSockaddr4 = (const struct sockaddr_in *)[someAddr4 bytes];
const struct sockaddr_in *cSockaddr4 = (const struct sockaddr_in *)[cachedConnectedAddress bytes];
if (memcmp(&sSockaddr4->sin_addr, &cSockaddr4->sin_addr, sizeof(struct in_addr)) != 0)
{
return NO;
}
if (memcmp(&sSockaddr4->sin_port, &cSockaddr4->sin_port, sizeof(in_port_t)) != 0)
{
return NO;
}
return YES;
}
- (BOOL)isConnectedToAddress6:(NSData *)someAddr6
{
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
NSAssert(flags & kDidConnect, @"Not connected");
NSAssert(cachedConnectedAddress, @"Expected cached connected address");
if (cachedConnectedFamily != AF_INET6)
{
return NO;
}
const struct sockaddr_in6 *sSockaddr6 = (const struct sockaddr_in6 *)[someAddr6 bytes];
const struct sockaddr_in6 *cSockaddr6 = (const struct sockaddr_in6 *)[cachedConnectedAddress bytes];
if (memcmp(&sSockaddr6->sin6_addr, &cSockaddr6->sin6_addr, sizeof(struct in6_addr)) != 0)
{
return NO;
}
if (memcmp(&sSockaddr6->sin6_port, &cSockaddr6->sin6_port, sizeof(in_port_t)) != 0)
{
return NO;
}
return YES;
}
- (unsigned int)indexOfInterfaceAddr4:(NSData *)interfaceAddr4
{
if (interfaceAddr4 == nil)
return 0;
if ([interfaceAddr4 length] != sizeof(struct sockaddr_in))
return 0;
int result = 0;
const struct sockaddr_in *ifaceAddr = (const struct sockaddr_in *)[interfaceAddr4 bytes];
struct ifaddrs *addrs;
const struct ifaddrs *cursor;
if ((getifaddrs(&addrs) == 0))
{
cursor = addrs;
while (cursor != NULL)
{
if (cursor->ifa_addr->sa_family == AF_INET)
{
// IPv4
const struct sockaddr_in *addr = (const struct sockaddr_in *)(const void *)cursor->ifa_addr;
if (memcmp(&addr->sin_addr, &ifaceAddr->sin_addr, sizeof(struct in_addr)) == 0)
{
result = if_nametoindex(cursor->ifa_name);
break;
}
}
cursor = cursor->ifa_next;
}
freeifaddrs(addrs);
}
return result;
}
- (unsigned int)indexOfInterfaceAddr6:(NSData *)interfaceAddr6
{
if (interfaceAddr6 == nil)
return 0;
if ([interfaceAddr6 length] != sizeof(struct sockaddr_in6))
return 0;
int result = 0;
const struct sockaddr_in6 *ifaceAddr = (const struct sockaddr_in6 *)[interfaceAddr6 bytes];
struct ifaddrs *addrs;
const struct ifaddrs *cursor;
if ((getifaddrs(&addrs) == 0))
{
cursor = addrs;
while (cursor != NULL)
{
if (cursor->ifa_addr->sa_family == AF_INET6)
{
// IPv6
const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)(const void *)cursor->ifa_addr;
if (memcmp(&addr->sin6_addr, &ifaceAddr->sin6_addr, sizeof(struct in6_addr)) == 0)
{
result = if_nametoindex(cursor->ifa_name);
break;
}
}
cursor = cursor->ifa_next;
}
freeifaddrs(addrs);
}
return result;
}
- (void)setupSendAndReceiveSourcesForSocket4
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
send4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socket4FD, 0, socketQueue);
receive4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket4FD, 0, socketQueue);
// Setup event handlers
dispatch_source_set_event_handler(send4Source, ^{ @autoreleasepool {
LogVerbose(@"send4EventBlock");
LogVerbose(@"dispatch_source_get_data(send4Source) = %lu", dispatch_source_get_data(send4Source));
self->flags |= kSock4CanAcceptBytes;
// If we're ready to send data, do so immediately.
// Otherwise pause the send source or it will continue to fire over and over again.
if (self->currentSend == nil)
{
LogVerbose(@"Nothing to send");
[self suspendSend4Source];
}
else if (self->currentSend->resolveInProgress)
{
LogVerbose(@"currentSend - waiting for address resolve");
[self suspendSend4Source];
}
else if (self->currentSend->filterInProgress)
{
LogVerbose(@"currentSend - waiting on sendFilter");
[self suspendSend4Source];
}
else
{
[self doSend];
}
}});
dispatch_source_set_event_handler(receive4Source, ^{ @autoreleasepool {
LogVerbose(@"receive4EventBlock");
self->socket4FDBytesAvailable = dispatch_source_get_data(self->receive4Source);
LogVerbose(@"socket4FDBytesAvailable: %lu", socket4FDBytesAvailable);
if (self->socket4FDBytesAvailable > 0)
[self doReceive];
else
[self doReceiveEOF];
}});
// Setup cancel handlers
__block int socketFDRefCount = 2;
int theSocketFD = socket4FD;
#if !OS_OBJECT_USE_OBJC
dispatch_source_t theSendSource = send4Source;
dispatch_source_t theReceiveSource = receive4Source;
#endif
dispatch_source_set_cancel_handler(send4Source, ^{
LogVerbose(@"send4CancelBlock");
#if !OS_OBJECT_USE_OBJC
LogVerbose(@"dispatch_release(send4Source)");
dispatch_release(theSendSource);
#endif
if (--socketFDRefCount == 0)
{
LogVerbose(@"close(socket4FD)");
close(theSocketFD);
}
});
dispatch_source_set_cancel_handler(receive4Source, ^{
LogVerbose(@"receive4CancelBlock");
#if !OS_OBJECT_USE_OBJC
LogVerbose(@"dispatch_release(receive4Source)");
dispatch_release(theReceiveSource);
#endif
if (--socketFDRefCount == 0)
{
LogVerbose(@"close(socket4FD)");
close(theSocketFD);
}
});
// We will not be able to receive until the socket is bound to a port,
// either explicitly via bind, or implicitly by connect or by sending data.
//
// But we should be able to send immediately.
socket4FDBytesAvailable = 0;
flags |= kSock4CanAcceptBytes;
flags |= kSend4SourceSuspended;
flags |= kReceive4SourceSuspended;
}
- (void)setupSendAndReceiveSourcesForSocket6
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
send6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socket6FD, 0, socketQueue);
receive6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket6FD, 0, socketQueue);
// Setup event handlers
dispatch_source_set_event_handler(send6Source, ^{ @autoreleasepool {
LogVerbose(@"send6EventBlock");
LogVerbose(@"dispatch_source_get_data(send6Source) = %lu", dispatch_source_get_data(send6Source));
self->flags |= kSock6CanAcceptBytes;
// If we're ready to send data, do so immediately.
// Otherwise pause the send source or it will continue to fire over and over again.
if (self->currentSend == nil)
{
LogVerbose(@"Nothing to send");
[self suspendSend6Source];
}
else if (self->currentSend->resolveInProgress)
{
LogVerbose(@"currentSend - waiting for address resolve");
[self suspendSend6Source];
}
else if (self->currentSend->filterInProgress)
{
LogVerbose(@"currentSend - waiting on sendFilter");
[self suspendSend6Source];
}
else
{
[self doSend];
}
}});
dispatch_source_set_event_handler(receive6Source, ^{ @autoreleasepool {
LogVerbose(@"receive6EventBlock");
self->socket6FDBytesAvailable = dispatch_source_get_data(self->receive6Source);
LogVerbose(@"socket6FDBytesAvailable: %lu", socket6FDBytesAvailable);
if (self->socket6FDBytesAvailable > 0)
[self doReceive];
else
[self doReceiveEOF];
}});
// Setup cancel handlers
__block int socketFDRefCount = 2;
int theSocketFD = socket6FD;
#if !OS_OBJECT_USE_OBJC
dispatch_source_t theSendSource = send6Source;
dispatch_source_t theReceiveSource = receive6Source;
#endif
dispatch_source_set_cancel_handler(send6Source, ^{
LogVerbose(@"send6CancelBlock");
#if !OS_OBJECT_USE_OBJC
LogVerbose(@"dispatch_release(send6Source)");
dispatch_release(theSendSource);
#endif
if (--socketFDRefCount == 0)
{
LogVerbose(@"close(socket6FD)");
close(theSocketFD);
}
});
dispatch_source_set_cancel_handler(receive6Source, ^{
LogVerbose(@"receive6CancelBlock");
#if !OS_OBJECT_USE_OBJC
LogVerbose(@"dispatch_release(receive6Source)");
dispatch_release(theReceiveSource);
#endif
if (--socketFDRefCount == 0)
{
LogVerbose(@"close(socket6FD)");
close(theSocketFD);
}
});
// We will not be able to receive until the socket is bound to a port,
// either explicitly via bind, or implicitly by connect or by sending data.
//
// But we should be able to send immediately.
socket6FDBytesAvailable = 0;
flags |= kSock6CanAcceptBytes;
flags |= kSend6SourceSuspended;
flags |= kReceive6SourceSuspended;
}
- (BOOL)createSocket4:(BOOL)useIPv4 socket6:(BOOL)useIPv6 error:(NSError * __autoreleasing *)errPtr
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
NSAssert(((flags & kDidCreateSockets) == 0), @"Sockets have already been created");
// CreateSocket Block
// This block will be invoked below.
int(^createSocket)(int) = ^int (int domain) {
int socketFD = socket(domain, SOCK_DGRAM, 0);
if (socketFD == SOCKET_NULL)
{
if (errPtr)
*errPtr = [self errnoErrorWithReason:@"Error in socket() function"];
return SOCKET_NULL;
}
int status;
// Set socket options
status = fcntl(socketFD, F_SETFL, O_NONBLOCK);
if (status == -1)
{
if (errPtr)
*errPtr = [self errnoErrorWithReason:@"Error enabling non-blocking IO on socket (fcntl)"];
close(socketFD);
return SOCKET_NULL;
}
int reuseaddr = 1;
status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr));
if (status == -1)
{
if (errPtr)
*errPtr = [self errnoErrorWithReason:@"Error enabling address reuse (setsockopt)"];
close(socketFD);
return SOCKET_NULL;
}
int nosigpipe = 1;
status = setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
if (status == -1)
{
if (errPtr)
*errPtr = [self errnoErrorWithReason:@"Error disabling sigpipe (setsockopt)"];
close(socketFD);
return SOCKET_NULL;
}
/**
* 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.
*
* The default maximum size of the UDP buffer in iOS is 9216 bytes.
*
* This is the reason of #222(GCD does not necessarily return the size of an entire UDP packet) and
* #535(GCDAsyncUDPSocket can not send data when data is greater than 9K)
*
*
* Enlarge the maximum size of UDP packet.
* I can not ensure the protocol type now so that the max size is set to 65535 :)
**/
status = setsockopt(socketFD, SOL_SOCKET, SO_SNDBUF, (const char*)&self->maxSendSize, sizeof(int));
if (status == -1)
{
if (errPtr)
*errPtr = [self errnoErrorWithReason:@"Error setting send buffer size (setsockopt)"];
close(socketFD);
return SOCKET_NULL;
}
status = setsockopt(socketFD, SOL_SOCKET, SO_RCVBUF, (const char*)&self->maxSendSize, sizeof(int));
if (status == -1)
{
if (errPtr)
*errPtr = [self errnoErrorWithReason:@"Error setting receive buffer size (setsockopt)"];
close(socketFD);
return SOCKET_NULL;
}
return socketFD;
};
// Create sockets depending upon given configuration.
if (useIPv4)
{
LogVerbose(@"Creating IPv4 socket");
socket4FD = createSocket(AF_INET);
if (socket4FD == SOCKET_NULL)
{
// errPtr set in local createSocket() block
return NO;
}
}
if (useIPv6)
{
LogVerbose(@"Creating IPv6 socket");
socket6FD = createSocket(AF_INET6);
if (socket6FD == SOCKET_NULL)
{
// errPtr set in local createSocket() block
if (socket4FD != SOCKET_NULL)
{
close(socket4FD);
socket4FD = SOCKET_NULL;
}
return NO;
}
}
// Setup send and receive sources
if (useIPv4)
[self setupSendAndReceiveSourcesForSocket4];
if (useIPv6)
[self setupSendAndReceiveSourcesForSocket6];
flags |= kDidCreateSockets;
return YES;
}
- (BOOL)createSockets:(NSError **)errPtr
{
LogTrace();
BOOL useIPv4 = [self isIPv4Enabled];
BOOL useIPv6 = [self isIPv6Enabled];
return [self createSocket4:useIPv4 socket6:useIPv6 error:errPtr];
}
- (void)suspendSend4Source
{
if (send4Source && !(flags & kSend4SourceSuspended))
{
LogVerbose(@"dispatch_suspend(send4Source)");
dispatch_suspend(send4Source);
flags |= kSend4SourceSuspended;
}
}
- (void)suspendSend6Source
{
if (send6Source && !(flags & kSend6SourceSuspended))
{
LogVerbose(@"dispatch_suspend(send6Source)");
dispatch_suspend(send6Source);
flags |= kSend6SourceSuspended;
}
}
- (void)resumeSend4Source
{
if (send4Source && (flags & kSend4SourceSuspended))
{
LogVerbose(@"dispatch_resume(send4Source)");
dispatch_resume(send4Source);
flags &= ~kSend4SourceSuspended;
}
}
- (void)resumeSend6Source
{
if (send6Source && (flags & kSend6SourceSuspended))
{
LogVerbose(@"dispatch_resume(send6Source)");
dispatch_resume(send6Source);
flags &= ~kSend6SourceSuspended;
}
}
- (void)suspendReceive4Source
{
if (receive4Source && !(flags & kReceive4SourceSuspended))
{
LogVerbose(@"dispatch_suspend(receive4Source)");
dispatch_suspend(receive4Source);
flags |= kReceive4SourceSuspended;
}
}
- (void)suspendReceive6Source
{
if (receive6Source && !(flags & kReceive6SourceSuspended))
{
LogVerbose(@"dispatch_suspend(receive6Source)");
dispatch_suspend(receive6Source);
flags |= kReceive6SourceSuspended;
}
}
- (void)resumeReceive4Source
{
if (receive4Source && (flags & kReceive4SourceSuspended))
{
LogVerbose(@"dispatch_resume(receive4Source)");
dispatch_resume(receive4Source);
flags &= ~kReceive4SourceSuspended;
}
}
- (void)resumeReceive6Source
{
if (receive6Source && (flags & kReceive6SourceSuspended))
{
LogVerbose(@"dispatch_resume(receive6Source)");
dispatch_resume(receive6Source);
flags &= ~kReceive6SourceSuspended;
}
}
- (void)closeSocket4
{
if (socket4FD != SOCKET_NULL)
{
LogVerbose(@"dispatch_source_cancel(send4Source)");
dispatch_source_cancel(send4Source);
LogVerbose(@"dispatch_source_cancel(receive4Source)");
dispatch_source_cancel(receive4Source);
// For some crazy reason (in my opinion), cancelling a dispatch source doesn't
// invoke the cancel handler if the dispatch source is paused.
// So we have to unpause the source if needed.
// This allows the cancel handler to be run, which in turn releases the source and closes the socket.
[self resumeSend4Source];
[self resumeReceive4Source];
// The sockets will be closed by the cancel handlers of the corresponding source
send4Source = NULL;
receive4Source = NULL;
socket4FD = SOCKET_NULL;
// Clear socket states
socket4FDBytesAvailable = 0;
flags &= ~kSock4CanAcceptBytes;
// Clear cached info
cachedLocalAddress4 = nil;
cachedLocalHost4 = nil;
cachedLocalPort4 = 0;
}
}
- (void)closeSocket6
{
if (socket6FD != SOCKET_NULL)
{
LogVerbose(@"dispatch_source_cancel(send6Source)");
dispatch_source_cancel(send6Source);
LogVerbose(@"dispatch_source_cancel(receive6Source)");
dispatch_source_cancel(receive6Source);
// For some crazy reason (in my opinion), cancelling a dispatch source doesn't
// invoke the cancel handler if the dispatch source is paused.
// So we have to unpause the source if needed.
// This allows the cancel handler to be run, which in turn releases the source and closes the socket.
[self resumeSend6Source];
[self resumeReceive6Source];
send6Source = NULL;
receive6Source = NULL;
// The sockets will be closed by the cancel handlers of the corresponding source
socket6FD = SOCKET_NULL;
// Clear socket states
socket6FDBytesAvailable = 0;
flags &= ~kSock6CanAcceptBytes;
// Clear cached info
cachedLocalAddress6 = nil;
cachedLocalHost6 = nil;
cachedLocalPort6 = 0;
}
}
- (void)closeSockets
{
[self closeSocket4];
[self closeSocket6];
flags &= ~kDidCreateSockets;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Diagnostics
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)getLocalAddress:(NSData **)dataPtr
host:(NSString **)hostPtr
port:(uint16_t *)portPtr
forSocket:(int)socketFD
withFamily:(int)socketFamily
{
NSData *data = nil;
NSString *host = nil;
uint16_t port = 0;
if (socketFamily == AF_INET)
{
struct sockaddr_in sockaddr4;
socklen_t sockaddr4len = sizeof(sockaddr4);
if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0)
{
data = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len];
host = [[self class] hostFromSockaddr4:&sockaddr4];
port = [[self class] portFromSockaddr4:&sockaddr4];
}
else
{
LogWarn(@"Error in getsockname: %@", [self errnoError]);
}
}
else if (socketFamily == AF_INET6)
{
struct sockaddr_in6 sockaddr6;
socklen_t sockaddr6len = sizeof(sockaddr6);
if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0)
{
data = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len];
host = [[self class] hostFromSockaddr6:&sockaddr6];
port = [[self class] portFromSockaddr6:&sockaddr6];
}
else
{
LogWarn(@"Error in getsockname: %@", [self errnoError]);
}
}
if (dataPtr) *dataPtr = data;
if (hostPtr) *hostPtr = host;
if (portPtr) *portPtr = port;
return (data != nil);
}
- (void)maybeUpdateCachedLocalAddress4Info
{
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
if ( cachedLocalAddress4 || ((flags & kDidBind) == 0) || (socket4FD == SOCKET_NULL) )
{
return;
}
NSData *address = nil;
NSString *host = nil;
uint16_t port = 0;
if ([self getLocalAddress:&address host:&host port:&port forSocket:socket4FD withFamily:AF_INET])
{
cachedLocalAddress4 = address;
cachedLocalHost4 = host;
cachedLocalPort4 = port;
}
}
- (void)maybeUpdateCachedLocalAddress6Info
{
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
if ( cachedLocalAddress6 || ((flags & kDidBind) == 0) || (socket6FD == SOCKET_NULL) )
{
return;
}
NSData *address = nil;
NSString *host = nil;
uint16_t port = 0;
if ([self getLocalAddress:&address host:&host port:&port forSocket:socket6FD withFamily:AF_INET6])
{
cachedLocalAddress6 = address;
cachedLocalHost6 = host;
cachedLocalPort6 = port;
}
}
- (NSData *)localAddress
{
__block NSData *result = nil;
dispatch_block_t block = ^{
if (self->socket4FD != SOCKET_NULL)
{
[self maybeUpdateCachedLocalAddress4Info];
result = self->cachedLocalAddress4;
}
else
{
[self maybeUpdateCachedLocalAddress6Info];
result = self->cachedLocalAddress6;
}
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, AutoreleasedBlock(block));
return result;
}
- (NSString *)localHost
{
__block NSString *result = nil;
dispatch_block_t block = ^{
if (self->socket4FD != SOCKET_NULL)
{
[self maybeUpdateCachedLocalAddress4Info];
result = self->cachedLocalHost4;
}
else
{
[self maybeUpdateCachedLocalAddress6Info];
result = self->cachedLocalHost6;
}
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, AutoreleasedBlock(block));
return result;
}
- (uint16_t)localPort
{
__block uint16_t result = 0;
dispatch_block_t block = ^{
if (self->socket4FD != SOCKET_NULL)
{
[self maybeUpdateCachedLocalAddress4Info];
result = self->cachedLocalPort4;
}
else
{
[self maybeUpdateCachedLocalAddress6Info];
result = self->cachedLocalPort6;
}
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, AutoreleasedBlock(block));
return result;
}
- (NSData *)localAddress_IPv4
{
__block NSData *result = nil;
dispatch_block_t block = ^{
[self maybeUpdateCachedLocalAddress4Info];
result = self->cachedLocalAddress4;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, AutoreleasedBlock(block));
return result;
}
- (NSString *)localHost_IPv4
{
__block NSString *result = nil;
dispatch_block_t block = ^{
[self maybeUpdateCachedLocalAddress4Info];
result = self->cachedLocalHost4;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, AutoreleasedBlock(block));
return result;
}
- (uint16_t)localPort_IPv4
{
__block uint16_t result = 0;
dispatch_block_t block = ^{
[self maybeUpdateCachedLocalAddress4Info];
result = self->cachedLocalPort4;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, AutoreleasedBlock(block));
return result;
}
- (NSData *)localAddress_IPv6
{
__block NSData *result = nil;
dispatch_block_t block = ^{
[self maybeUpdateCachedLocalAddress6Info];
result = self->cachedLocalAddress6;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, AutoreleasedBlock(block));
return result;
}
- (NSString *)localHost_IPv6
{
__block NSString *result = nil;
dispatch_block_t block = ^{
[self maybeUpdateCachedLocalAddress6Info];
result = self->cachedLocalHost6;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, AutoreleasedBlock(block));
return result;
}
- (uint16_t)localPort_IPv6
{
__block uint16_t result = 0;
dispatch_block_t block = ^{
[self maybeUpdateCachedLocalAddress6Info];
result = self->cachedLocalPort6;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, AutoreleasedBlock(block));
return result;
}
- (void)maybeUpdateCachedConnectedAddressInfo
{
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
if (cachedConnectedAddress || (flags & kDidConnect) == 0)
{
return;
}
NSData *data = nil;
NSString *host = nil;
uint16_t port = 0;
int family = AF_UNSPEC;
if (socket4FD != SOCKET_NULL)
{
struct sockaddr_in sockaddr4;
socklen_t sockaddr4len = sizeof(sockaddr4);
if (getpeername(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0)
{
data = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len];
host = [[self class] hostFromSockaddr4:&sockaddr4];
port = [[self class] portFromSockaddr4:&sockaddr4];
family = AF_INET;
}
else
{
LogWarn(@"Error in getpeername: %@", [self errnoError]);
}
}
else if (socket6FD != SOCKET_NULL)
{
struct sockaddr_in6 sockaddr6;
socklen_t sockaddr6len = sizeof(sockaddr6);
if (getpeername(socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0)
{
data = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len];
host = [[self class] hostFromSockaddr6:&sockaddr6];
port = [[self class] portFromSockaddr6:&sockaddr6];
family = AF_INET6;
}
else
{
LogWarn(@"Error in getpeername: %@", [self errnoError]);
}
}
cachedConnectedAddress = data;
cachedConnectedHost = host;
cachedConnectedPort = port;
cachedConnectedFamily = family;
}
- (NSData *)connectedAddress
{
__block NSData *result = nil;
dispatch_block_t block = ^{
[self maybeUpdateCachedConnectedAddressInfo];
result = self->cachedConnectedAddress;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, AutoreleasedBlock(block));
return result;
}
- (NSString *)connectedHost
{
__block NSString *result = nil;
dispatch_block_t block = ^{
[self maybeUpdateCachedConnectedAddressInfo];
result = self->cachedConnectedHost;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, AutoreleasedBlock(block));
return result;
}
- (uint16_t)connectedPort
{
__block uint16_t result = 0;
dispatch_block_t block = ^{
[self maybeUpdateCachedConnectedAddressInfo];
result = self->cachedConnectedPort;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, AutoreleasedBlock(block));
return result;
}
- (BOOL)isConnected
{
__block BOOL result = NO;
dispatch_block_t block = ^{
result = (self->flags & kDidConnect) ? YES : NO;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (BOOL)isClosed
{
__block BOOL result = YES;
dispatch_block_t block = ^{
result = (self->flags & kDidCreateSockets) ? NO : YES;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (BOOL)isIPv4
{
__block BOOL result = NO;
dispatch_block_t block = ^{
if (self->flags & kDidCreateSockets)
{
result = (self->socket4FD != SOCKET_NULL);
}
else
{
result = [self isIPv4Enabled];
}
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (BOOL)isIPv6
{
__block BOOL result = NO;
dispatch_block_t block = ^{
if (self->flags & kDidCreateSockets)
{
result = (self->socket6FD != SOCKET_NULL);
}
else
{
result = [self isIPv6Enabled];
}
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Binding
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* This method runs through the various checks required prior to a bind attempt.
* It is shared between the various bind methods.
**/
- (BOOL)preBind:(NSError **)errPtr
{
if (![self preOp:errPtr])
{
return NO;
}
if (flags & kDidBind)
{
if (errPtr)
{
NSString *msg = @"Cannot bind a socket more than once.";
*errPtr = [self badConfigError:msg];
}
return NO;
}
if ((flags & kConnecting) || (flags & kDidConnect))
{
if (errPtr)
{
NSString *msg = @"Cannot bind after connecting. If needed, bind first, then connect.";
*errPtr = [self badConfigError:msg];
}
return NO;
}
BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled
{
if (errPtr)
{
NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first.";
*errPtr = [self badConfigError:msg];
}
return NO;
}
return YES;
}
- (BOOL)bindToPort:(uint16_t)port error:(NSError **)errPtr
{
return [self bindToPort:port interface:nil error:errPtr];
}
- (BOOL)bindToPort:(uint16_t)port interface:(NSString *)interface error:(NSError **)errPtr
{
__block BOOL result = NO;
__block NSError *err = nil;
dispatch_block_t block = ^{ @autoreleasepool {
// Run through sanity checks
if (![self preBind:&err])
{
return_from_block;
}
// Check the given interface
NSData *interface4 = nil;
NSData *interface6 = nil;
[self convertIntefaceDescription:interface port:port intoAddress4:&interface4 address6:&interface6];
if ((interface4 == nil) && (interface6 == nil))
{
NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
err = [self badParamError:msg];
return_from_block;
}
BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO;
BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO;
if (isIPv4Disabled && (interface6 == nil))
{
NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6.";
err = [self badParamError:msg];
return_from_block;
}
if (isIPv6Disabled && (interface4 == nil))
{
NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4.";
err = [self badParamError:msg];
return_from_block;
}
// Determine protocol(s)
BOOL useIPv4 = !isIPv4Disabled && (interface4 != nil);
BOOL useIPv6 = !isIPv6Disabled && (interface6 != nil);
// Create the socket(s) if needed
if ((self->flags & kDidCreateSockets) == 0)
{
if (![self createSocket4:useIPv4 socket6:useIPv6 error:&err])
{
return_from_block;
}
}
// Bind the socket(s)
LogVerbose(@"Binding socket to port(%hu) interface(%@)", port, interface);
if (useIPv4)
{
int status = bind(self->socket4FD, (const struct sockaddr *)[interface4 bytes], (socklen_t)[interface4 length]);
if (status == -1)
{
[self closeSockets];
NSString *reason = @"Error in bind() function";
err = [self errnoErrorWithReason:reason];
return_from_block;
}
}
if (useIPv6)
{
int status = bind(self->socket6FD, (const struct sockaddr *)[interface6 bytes], (socklen_t)[interface6 length]);
if (status == -1)
{
[self closeSockets];
NSString *reason = @"Error in bind() function";
err = [self errnoErrorWithReason:reason];
return_from_block;
}
}
// Update flags
self->flags |= kDidBind;
if (!useIPv4) self->flags |= kIPv4Deactivated;
if (!useIPv6) self->flags |= kIPv6Deactivated;
result = YES;
}};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
if (err)
LogError(@"Error binding to port/interface: %@", err);
if (errPtr)
*errPtr = err;
return result;
}
- (BOOL)bindToAddress:(NSData *)localAddr error:(NSError **)errPtr
{
__block BOOL result = NO;
__block NSError *err = nil;
dispatch_block_t block = ^{ @autoreleasepool {
// Run through sanity checks
if (![self preBind:&err])
{
return_from_block;
}
// Check the given address
int addressFamily = [[self class] familyFromAddress:localAddr];
if (addressFamily == AF_UNSPEC)
{
NSString *msg = @"A valid IPv4 or IPv6 address was not given";
err = [self badParamError:msg];
return_from_block;
}
NSData *localAddr4 = (addressFamily == AF_INET) ? localAddr : nil;
NSData *localAddr6 = (addressFamily == AF_INET6) ? localAddr : nil;
BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO;
BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO;
if (isIPv4Disabled && localAddr4)
{
NSString *msg = @"IPv4 has been disabled and an IPv4 address was passed.";
err = [self badParamError:msg];
return_from_block;
}
if (isIPv6Disabled && localAddr6)
{
NSString *msg = @"IPv6 has been disabled and an IPv6 address was passed.";
err = [self badParamError:msg];
return_from_block;
}
// Determine protocol(s)
BOOL useIPv4 = !isIPv4Disabled && (localAddr4 != nil);
BOOL useIPv6 = !isIPv6Disabled && (localAddr6 != nil);
// Create the socket(s) if needed
if ((self->flags & kDidCreateSockets) == 0)
{
if (![self createSocket4:useIPv4 socket6:useIPv6 error:&err])
{
return_from_block;
}
}
// Bind the socket(s)
if (useIPv4)
{
LogVerbose(@"Binding socket to address(%@:%hu)",
[[self class] hostFromAddress:localAddr4],
[[self class] portFromAddress:localAddr4]);
int status = bind(self->socket4FD, (const struct sockaddr *)[localAddr4 bytes], (socklen_t)[localAddr4 length]);
if (status == -1)
{
[self closeSockets];
NSString *reason = @"Error in bind() function";
err = [self errnoErrorWithReason:reason];
return_from_block;
}
}
else
{
LogVerbose(@"Binding socket to address(%@:%hu)",
[[self class] hostFromAddress:localAddr6],
[[self class] portFromAddress:localAddr6]);
int status = bind(self->socket6FD, (const struct sockaddr *)[localAddr6 bytes], (socklen_t)[localAddr6 length]);
if (status == -1)
{
[self closeSockets];
NSString *reason = @"Error in bind() function";
err = [self errnoErrorWithReason:reason];
return_from_block;
}
}
// Update flags
self->flags |= kDidBind;
if (!useIPv4) self->flags |= kIPv4Deactivated;
if (!useIPv6) self->flags |= kIPv6Deactivated;
result = YES;
}};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
if (err)
LogError(@"Error binding to address: %@", err);
if (errPtr)
*errPtr = err;
return result;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Connecting
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* This method runs through the various checks required prior to a connect attempt.
* It is shared between the various connect methods.
**/
- (BOOL)preConnect:(NSError **)errPtr
{
if (![self preOp:errPtr])
{
return NO;
}
if ((flags & kConnecting) || (flags & kDidConnect))
{
if (errPtr)
{
NSString *msg = @"Cannot connect a socket more than once.";
*errPtr = [self badConfigError:msg];
}
return NO;
}
BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled
{
if (errPtr)
{
NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first.";
*errPtr = [self badConfigError:msg];
}
return NO;
}
return YES;
}
- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr
{
__block BOOL result = NO;
__block NSError *err = nil;
dispatch_block_t block = ^{ @autoreleasepool {
// Run through sanity checks.
if (![self preConnect:&err])
{
return_from_block;
}
// Check parameter(s)
if (host == nil)
{
NSString *msg = @"The host param is nil. Should be domain name or IP address string.";
err = [self badParamError:msg];
return_from_block;
}
// Create the socket(s) if needed
if ((self->flags & kDidCreateSockets) == 0)
{
if (![self createSockets:&err])
{
return_from_block;
}
}
// Create special connect packet
GCDAsyncUdpSpecialPacket *packet = [[GCDAsyncUdpSpecialPacket alloc] init];
packet->resolveInProgress = YES;
// Start asynchronous DNS resolve for host:port on background queue
LogVerbose(@"Dispatching DNS resolve for connect...");
[self asyncResolveHost:host port:port withCompletionBlock:^(NSArray *addresses, NSError *error) {
// The asyncResolveHost:port:: method asynchronously dispatches a task onto the global concurrent queue,
// and immediately returns. Once the async resolve task completes,
// this block is executed on our socketQueue.
packet->resolveInProgress = NO;
packet->addresses = addresses;
packet->error = error;
[self maybeConnect];
}];
// Updates flags, add connect packet to send queue, and pump send queue
self->flags |= kConnecting;
[self->sendQueue addObject:packet];
[self maybeDequeueSend];
result = YES;
}};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
if (err)
LogError(@"Error connecting to host/port: %@", err);
if (errPtr)
*errPtr = err;
return result;
}
- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr
{
__block BOOL result = NO;
__block NSError *err = nil;
dispatch_block_t block = ^{ @autoreleasepool {
// Run through sanity checks.
if (![self preConnect:&err])
{
return_from_block;
}
// Check parameter(s)
if (remoteAddr == nil)
{
NSString *msg = @"The address param is nil. Should be a valid address.";
err = [self badParamError:msg];
return_from_block;
}
// Create the socket(s) if needed
if ((self->flags & kDidCreateSockets) == 0)
{
if (![self createSockets:&err])
{
return_from_block;
}
}
// The remoteAddr parameter could be of type NSMutableData.
// So we copy it to be safe.
NSData *address = [remoteAddr copy];
NSArray *addresses = [NSArray arrayWithObject:address];
GCDAsyncUdpSpecialPacket *packet = [[GCDAsyncUdpSpecialPacket alloc] init];
packet->addresses = addresses;
// Updates flags, add connect packet to send queue, and pump send queue
self->flags |= kConnecting;
[self->sendQueue addObject:packet];
[self maybeDequeueSend];
result = YES;
}};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
if (err)
LogError(@"Error connecting to address: %@", err);
if (errPtr)
*errPtr = err;
return result;
}
- (void)maybeConnect
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
BOOL sendQueueReady = [currentSend isKindOfClass:[GCDAsyncUdpSpecialPacket class]];
if (sendQueueReady)
{
GCDAsyncUdpSpecialPacket *connectPacket = (GCDAsyncUdpSpecialPacket *)currentSend;
if (connectPacket->resolveInProgress)
{
LogVerbose(@"Waiting for DNS resolve...");
}
else
{
if (connectPacket->error)
{
[self notifyDidNotConnect:connectPacket->error];
}
else
{
NSData *address = nil;
NSError *error = nil;
int addressFamily = [self getAddress:&address error:&error fromAddresses:connectPacket->addresses];
// Perform connect
BOOL result = NO;
switch (addressFamily)
{
case AF_INET : result = [self connectWithAddress4:address error:&error]; break;
case AF_INET6 : result = [self connectWithAddress6:address error:&error]; break;
}
if (result)
{
flags |= kDidBind;
flags |= kDidConnect;
cachedConnectedAddress = address;
cachedConnectedHost = [[self class] hostFromAddress:address];
cachedConnectedPort = [[self class] portFromAddress:address];
cachedConnectedFamily = addressFamily;
[self notifyDidConnectToAddress:address];
}
else
{
[self notifyDidNotConnect:error];
}
}
flags &= ~kConnecting;
[self endCurrentSend];
[self maybeDequeueSend];
}
}
}
- (BOOL)connectWithAddress4:(NSData *)address4 error:(NSError **)errPtr
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
int status = connect(socket4FD, (const struct sockaddr *)[address4 bytes], (socklen_t)[address4 length]);
if (status != 0)
{
if (errPtr)
*errPtr = [self errnoErrorWithReason:@"Error in connect() function"];
return NO;
}
[self closeSocket6];
flags |= kIPv6Deactivated;
return YES;
}
- (BOOL)connectWithAddress6:(NSData *)address6 error:(NSError **)errPtr
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
int status = connect(socket6FD, (const struct sockaddr *)[address6 bytes], (socklen_t)[address6 length]);
if (status != 0)
{
if (errPtr)
*errPtr = [self errnoErrorWithReason:@"Error in connect() function"];
return NO;
}
[self closeSocket4];
flags |= kIPv4Deactivated;
return YES;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Multicast
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)preJoin:(NSError **)errPtr
{
if (![self preOp:errPtr])
{
return NO;
}
if (!(flags & kDidBind))
{
if (errPtr)
{
NSString *msg = @"Must bind a socket before joining a multicast group.";
*errPtr = [self badConfigError:msg];
}
return NO;
}
if ((flags & kConnecting) || (flags & kDidConnect))
{
if (errPtr)
{
NSString *msg = @"Cannot join a multicast group if connected.";
*errPtr = [self badConfigError:msg];
}
return NO;
}
return YES;
}
- (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr
{
return [self joinMulticastGroup:group onInterface:nil error:errPtr];
}
- (BOOL)joinMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr
{
// IP_ADD_MEMBERSHIP == IPV6_JOIN_GROUP
return [self performMulticastRequest:IP_ADD_MEMBERSHIP forGroup:group onInterface:interface error:errPtr];
}
- (BOOL)leaveMulticastGroup:(NSString *)group error:(NSError **)errPtr
{
return [self leaveMulticastGroup:group onInterface:nil error:errPtr];
}
- (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr
{
// IP_DROP_MEMBERSHIP == IPV6_LEAVE_GROUP
return [self performMulticastRequest:IP_DROP_MEMBERSHIP forGroup:group onInterface:interface error:errPtr];
}
- (BOOL)performMulticastRequest:(int)requestType
forGroup:(NSString *)group
onInterface:(NSString *)interface
error:(NSError **)errPtr
{
__block BOOL result = NO;
__block NSError *err = nil;
dispatch_block_t block = ^{ @autoreleasepool {
// Run through sanity checks
if (![self preJoin:&err])
{
return_from_block;
}
// Convert group to address
NSData *groupAddr4 = nil;
NSData *groupAddr6 = nil;
[self convertNumericHost:group port:0 intoAddress4:&groupAddr4 address6:&groupAddr6];
if ((groupAddr4 == nil) && (groupAddr6 == nil))
{
NSString *msg = @"Unknown group. Specify valid group IP address.";
err = [self badParamError:msg];
return_from_block;
}
// Convert interface to address
NSData *interfaceAddr4 = nil;
NSData *interfaceAddr6 = nil;
[self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6];
if ((interfaceAddr4 == nil) && (interfaceAddr6 == nil))
{
NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
err = [self badParamError:msg];
return_from_block;
}
// Perform join
if ((self->socket4FD != SOCKET_NULL) && groupAddr4 && interfaceAddr4)
{
const struct sockaddr_in *nativeGroup = (const struct sockaddr_in *)[groupAddr4 bytes];
const struct sockaddr_in *nativeIface = (const struct sockaddr_in *)[interfaceAddr4 bytes];
struct ip_mreq imreq;
imreq.imr_multiaddr = nativeGroup->sin_addr;
imreq.imr_interface = nativeIface->sin_addr;
int status = setsockopt(self->socket4FD, IPPROTO_IP, requestType, (const void *)&imreq, sizeof(imreq));
if (status != 0)
{
err = [self errnoErrorWithReason:@"Error in setsockopt() function"];
return_from_block;
}
// Using IPv4 only
[self closeSocket6];
result = YES;
}
else if ((self->socket6FD != SOCKET_NULL) && groupAddr6 && interfaceAddr6)
{
const struct sockaddr_in6 *nativeGroup = (const struct sockaddr_in6 *)[groupAddr6 bytes];
struct ipv6_mreq imreq;
imreq.ipv6mr_multiaddr = nativeGroup->sin6_addr;
imreq.ipv6mr_interface = [self indexOfInterfaceAddr6:interfaceAddr6];
int status = setsockopt(self->socket6FD, IPPROTO_IPV6, requestType, (const void *)&imreq, sizeof(imreq));
if (status != 0)
{
err = [self errnoErrorWithReason:@"Error in setsockopt() function"];
return_from_block;
}
// Using IPv6 only
[self closeSocket4];
result = YES;
}
else
{
NSString *msg = @"Socket, group, and interface do not have matching IP versions";
err = [self badParamError:msg];
return_from_block;
}
}};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
if (errPtr)
*errPtr = err;
return result;
}
- (BOOL)sendIPv4MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr
{
__block BOOL result = NO;
__block NSError *err = nil;
dispatch_block_t block = ^{ @autoreleasepool {
if (![self preOp:&err])
{
return_from_block;
}
if ((self->flags & kDidCreateSockets) == 0)
{
if (![self createSockets:&err])
{
return_from_block;
}
}
// Convert interface to address
NSData *interfaceAddr4 = nil;
NSData *interfaceAddr6 = nil;
[self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6];
if (interfaceAddr4 == nil)
{
NSString *msg = @"Unknown interface. Specify valid interface by IP address.";
err = [self badParamError:msg];
return_from_block;
}
if (self->socket4FD != SOCKET_NULL) {
const struct sockaddr_in *nativeIface = (struct sockaddr_in *)[interfaceAddr4 bytes];
struct in_addr interface_addr = nativeIface->sin_addr;
int status = setsockopt(self->socket4FD, IPPROTO_IP, IP_MULTICAST_IF, &interface_addr, sizeof(interface_addr));
if (status != 0) {
err = [self errnoErrorWithReason:@"Error in setsockopt() function"];
return_from_block;
result = YES;
}
}
}};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
if (errPtr)
*errPtr = err;
return result;
}
- (BOOL)sendIPv6MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr
{
__block BOOL result = NO;
__block NSError *err = nil;
dispatch_block_t block = ^{ @autoreleasepool {
if (![self preOp:&err])
{
return_from_block;
}
if ((self->flags & kDidCreateSockets) == 0)
{
if (![self createSockets:&err])
{
return_from_block;
}
}
// Convert interface to address
NSData *interfaceAddr4 = nil;
NSData *interfaceAddr6 = nil;
[self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6];
if (interfaceAddr6 == nil)
{
NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\").";
err = [self badParamError:msg];
return_from_block;
}
if ((self->socket6FD != SOCKET_NULL)) {
uint32_t scope_id = [self indexOfInterfaceAddr6:interfaceAddr6];
int status = setsockopt(self->socket6FD, IPPROTO_IPV6, IPV6_MULTICAST_IF, &scope_id, sizeof(scope_id));
if (status != 0) {
err = [self errnoErrorWithReason:@"Error in setsockopt() function"];
return_from_block;
}
result = YES;
}
}};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
if (errPtr)
*errPtr = err;
return result;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Reuse port
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)enableReusePort:(BOOL)flag error:(NSError **)errPtr
{
__block BOOL result = NO;
__block NSError *err = nil;
dispatch_block_t block = ^{ @autoreleasepool {
if (![self preOp:&err])
{
return_from_block;
}
if ((self->flags & kDidCreateSockets) == 0)
{
if (![self createSockets:&err])
{
return_from_block;
}
}
int value = flag ? 1 : 0;
if (self->socket4FD != SOCKET_NULL)
{
int error = setsockopt(self->socket4FD, SOL_SOCKET, SO_REUSEPORT, (const void *)&value, sizeof(value));
if (error)
{
err = [self errnoErrorWithReason:@"Error in setsockopt() function"];
return_from_block;
}
result = YES;
}
if (self->socket6FD != SOCKET_NULL)
{
int error = setsockopt(self->socket6FD, SOL_SOCKET, SO_REUSEPORT, (const void *)&value, sizeof(value));
if (error)
{
err = [self errnoErrorWithReason:@"Error in setsockopt() function"];
return_from_block;
}
result = YES;
}
}};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
if (errPtr)
*errPtr = err;
return result;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Broadcast
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr
{
__block BOOL result = NO;
__block NSError *err = nil;
dispatch_block_t block = ^{ @autoreleasepool {
if (![self preOp:&err])
{
return_from_block;
}
if ((self->flags & kDidCreateSockets) == 0)
{
if (![self createSockets:&err])
{
return_from_block;
}
}
if (self->socket4FD != SOCKET_NULL)
{
int value = flag ? 1 : 0;
int error = setsockopt(self->socket4FD, SOL_SOCKET, SO_BROADCAST, (const void *)&value, sizeof(value));
if (error)
{
err = [self errnoErrorWithReason:@"Error in setsockopt() function"];
return_from_block;
}
result = YES;
}
// IPv6 does not implement broadcast, the ability to send a packet to all hosts on the attached link.
// The same effect can be achieved by sending a packet to the link-local all hosts multicast group.
}};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
if (errPtr)
*errPtr = err;
return result;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Sending
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)sendData:(NSData *)data withTag:(long)tag
{
[self sendData:data withTimeout:-1.0 tag:tag];
}
- (void)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag
{
LogTrace();
if ([data length] == 0)
{
LogWarn(@"Ignoring attempt to send nil/empty data.");
return;
}
GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag];
dispatch_async(socketQueue, ^{ @autoreleasepool {
[self->sendQueue addObject:packet];
[self maybeDequeueSend];
}});
}
- (void)sendData:(NSData *)data
toHost:(NSString *)host
port:(uint16_t)port
withTimeout:(NSTimeInterval)timeout
tag:(long)tag
{
LogTrace();
if ([data length] == 0)
{
LogWarn(@"Ignoring attempt to send nil/empty data.");
return;
}
GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag];
packet->resolveInProgress = YES;
[self asyncResolveHost:host port:port withCompletionBlock:^(NSArray *addresses, NSError *error) {
// The asyncResolveHost:port:: method asynchronously dispatches a task onto the global concurrent queue,
// and immediately returns. Once the async resolve task completes,
// this block is executed on our socketQueue.
packet->resolveInProgress = NO;
packet->resolvedAddresses = addresses;
packet->resolveError = error;
if (packet == self->currentSend)
{
LogVerbose(@"currentSend - address resolved");
[self doPreSend];
}
}];
dispatch_async(socketQueue, ^{ @autoreleasepool {
[self->sendQueue addObject:packet];
[self maybeDequeueSend];
}});
}
- (void)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag
{
LogTrace();
if ([data length] == 0)
{
LogWarn(@"Ignoring attempt to send nil/empty data.");
return;
}
GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag];
packet->addressFamily = [GCDAsyncUdpSocket familyFromAddress:remoteAddr];
packet->address = remoteAddr;
dispatch_async(socketQueue, ^{ @autoreleasepool {
[self->sendQueue addObject:packet];
[self maybeDequeueSend];
}});
}
- (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue
{
[self setSendFilter:filterBlock withQueue:filterQueue isAsynchronous:YES];
}
- (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock
withQueue:(dispatch_queue_t)filterQueue
isAsynchronous:(BOOL)isAsynchronous
{
GCDAsyncUdpSocketSendFilterBlock newFilterBlock = NULL;
dispatch_queue_t newFilterQueue = NULL;
if (filterBlock)
{
NSAssert(filterQueue, @"Must provide a dispatch_queue in which to run the filter block.");
newFilterBlock = [filterBlock copy];
newFilterQueue = filterQueue;
#if !OS_OBJECT_USE_OBJC
dispatch_retain(newFilterQueue);
#endif
}
dispatch_block_t block = ^{
#if !OS_OBJECT_USE_OBJC
if (self->sendFilterQueue) dispatch_release(self->sendFilterQueue);
#endif
self->sendFilterBlock = newFilterBlock;
self->sendFilterQueue = newFilterQueue;
self->sendFilterAsync = isAsynchronous;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
- (void)maybeDequeueSend
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
// If we don't have a send operation already in progress
if (currentSend == nil)
{
// Create the sockets if needed
if ((flags & kDidCreateSockets) == 0)
{
NSError *err = nil;
if (![self createSockets:&err])
{
[self closeWithError:err];
return;
}
}
while ([sendQueue count] > 0)
{
// Dequeue the next object in the queue
currentSend = [sendQueue objectAtIndex:0];
[sendQueue removeObjectAtIndex:0];
if ([currentSend isKindOfClass:[GCDAsyncUdpSpecialPacket class]])
{
[self maybeConnect];
return; // The maybeConnect method, if it connects, will invoke this method again
}
else if (currentSend->resolveError)
{
// Notify delegate
[self notifyDidNotSendDataWithTag:currentSend->tag dueToError:currentSend->resolveError];
// Clear currentSend
currentSend = nil;
continue;
}
else
{
// Start preprocessing checks on the send packet
[self doPreSend];
break;
}
}
if ((currentSend == nil) && (flags & kCloseAfterSends))
{
[self closeWithError:nil];
}
}
}
/**
* This method is called after a sendPacket has been dequeued.
* It performs various preprocessing checks on the packet,
* and queries the sendFilter (if set) to determine if the packet can be sent.
*
* If the packet passes all checks, it will be passed on to the doSend method.
**/
- (void)doPreSend
{
LogTrace();
//
// 1. Check for problems with send packet
//
BOOL waitingForResolve = NO;
NSError *error = nil;
if (flags & kDidConnect)
{
// Connected socket
if (currentSend->resolveInProgress || currentSend->resolvedAddresses || currentSend->resolveError)
{
NSString *msg = @"Cannot specify destination of packet for connected socket";
error = [self badConfigError:msg];
}
else
{
currentSend->address = cachedConnectedAddress;
currentSend->addressFamily = cachedConnectedFamily;
}
}
else
{
// Non-Connected socket
if (currentSend->resolveInProgress)
{
// We're waiting for the packet's destination to be resolved.
waitingForResolve = YES;
}
else if (currentSend->resolveError)
{
error = currentSend->resolveError;
}
else if (currentSend->address == nil)
{
if (currentSend->resolvedAddresses == nil)
{
NSString *msg = @"You must specify destination of packet for a non-connected socket";
error = [self badConfigError:msg];
}
else
{
// Pick the proper address to use (out of possibly several resolved addresses)
NSData *address = nil;
int addressFamily = AF_UNSPEC;
addressFamily = [self getAddress:&address error:&error fromAddresses:currentSend->resolvedAddresses];
currentSend->address = address;
currentSend->addressFamily = addressFamily;
}
}
}
if (waitingForResolve)
{
// We're waiting for the packet's destination to be resolved.
LogVerbose(@"currentSend - waiting for address resolve");
if (flags & kSock4CanAcceptBytes) {
[self suspendSend4Source];
}
if (flags & kSock6CanAcceptBytes) {
[self suspendSend6Source];
}
return;
}
if (error)
{
// Unable to send packet due to some error.
// Notify delegate and move on.
[self notifyDidNotSendDataWithTag:currentSend->tag dueToError:error];
[self endCurrentSend];
[self maybeDequeueSend];
return;
}
//
// 2. Query sendFilter (if applicable)
//
if (sendFilterBlock && sendFilterQueue)
{
// Query sendFilter
if (sendFilterAsync)
{
// Scenario 1 of 3 - Need to asynchronously query sendFilter
currentSend->filterInProgress = YES;
GCDAsyncUdpSendPacket *sendPacket = currentSend;
dispatch_async(sendFilterQueue, ^{ @autoreleasepool {
BOOL allowed = self->sendFilterBlock(sendPacket->buffer, sendPacket->address, sendPacket->tag);
dispatch_async(self->socketQueue, ^{ @autoreleasepool {
sendPacket->filterInProgress = NO;
if (sendPacket == self->currentSend)
{
if (allowed)
{
[self doSend];
}
else
{
LogVerbose(@"currentSend - silently dropped by sendFilter");
[self notifyDidSendDataWithTag:self->currentSend->tag];
[self endCurrentSend];
[self maybeDequeueSend];
}
}
}});
}});
}
else
{
// Scenario 2 of 3 - Need to synchronously query sendFilter
__block BOOL allowed = YES;
dispatch_sync(sendFilterQueue, ^{ @autoreleasepool {
allowed = self->sendFilterBlock(self->currentSend->buffer, self->currentSend->address, self->currentSend->tag);
}});
if (allowed)
{
[self doSend];
}
else
{
LogVerbose(@"currentSend - silently dropped by sendFilter");
[self notifyDidSendDataWithTag:currentSend->tag];
[self endCurrentSend];
[self maybeDequeueSend];
}
}
}
else // if (!sendFilterBlock || !sendFilterQueue)
{
// Scenario 3 of 3 - No sendFilter. Just go straight into sending.
[self doSend];
}
}
/**
* This method performs the actual sending of data in the currentSend packet.
* It should only be called if the
**/
- (void)doSend
{
LogTrace();
NSAssert(currentSend != nil, @"Invalid logic");
// Perform the actual send
ssize_t result = 0;
if (flags & kDidConnect)
{
// Connected socket
const void *buffer = [currentSend->buffer bytes];
size_t length = (size_t)[currentSend->buffer length];
if (currentSend->addressFamily == AF_INET)
{
result = send(socket4FD, buffer, length, 0);
LogVerbose(@"send(socket4FD) = %d", result);
}
else
{
result = send(socket6FD, buffer, length, 0);
LogVerbose(@"send(socket6FD) = %d", result);
}
}
else
{
// Non-Connected socket
const void *buffer = [currentSend->buffer bytes];
size_t length = (size_t)[currentSend->buffer length];
const void *dst = [currentSend->address bytes];
socklen_t dstSize = (socklen_t)[currentSend->address length];
if (currentSend->addressFamily == AF_INET)
{
result = sendto(socket4FD, buffer, length, 0, dst, dstSize);
LogVerbose(@"sendto(socket4FD) = %d", result);
}
else
{
result = sendto(socket6FD, buffer, length, 0, dst, dstSize);
LogVerbose(@"sendto(socket6FD) = %d", result);
}
}
// If the socket wasn't bound before, it is now
if ((flags & kDidBind) == 0)
{
flags |= kDidBind;
}
// Check the results.
//
// From the send() & sendto() manpage:
//
// Upon successful completion, the number of bytes which were sent is returned.
// Otherwise, -1 is returned and the global variable errno is set to indicate the error.
BOOL waitingForSocket = NO;
NSError *socketError = nil;
if (result == 0)
{
waitingForSocket = YES;
}
else if (result < 0)
{
if (errno == EAGAIN)
waitingForSocket = YES;
else
socketError = [self errnoErrorWithReason:@"Error in send() function."];
}
if (waitingForSocket)
{
// Not enough room in the underlying OS socket send buffer.
// Wait for a notification of available space.
LogVerbose(@"currentSend - waiting for socket");
if (!(flags & kSock4CanAcceptBytes)) {
[self resumeSend4Source];
}
if (!(flags & kSock6CanAcceptBytes)) {
[self resumeSend6Source];
}
if ((sendTimer == NULL) && (currentSend->timeout >= 0.0))
{
// Unable to send packet right away.
// Start timer to timeout the send operation.
[self setupSendTimerWithTimeout:currentSend->timeout];
}
}
else if (socketError)
{
[self closeWithError:socketError];
}
else // done
{
[self notifyDidSendDataWithTag:currentSend->tag];
[self endCurrentSend];
[self maybeDequeueSend];
}
}
/**
* Releases all resources associated with the currentSend.
**/
- (void)endCurrentSend
{
if (sendTimer)
{
dispatch_source_cancel(sendTimer);
#if !OS_OBJECT_USE_OBJC
dispatch_release(sendTimer);
#endif
sendTimer = NULL;
}
currentSend = nil;
}
/**
* Performs the operations to timeout the current send operation, and move on.
**/
- (void)doSendTimeout
{
LogTrace();
[self notifyDidNotSendDataWithTag:currentSend->tag dueToError:[self sendTimeoutError]];
[self endCurrentSend];
[self maybeDequeueSend];
}
/**
* Sets up a timer that fires to timeout the current send operation.
* This method should only be called once per send packet.
**/
- (void)setupSendTimerWithTimeout:(NSTimeInterval)timeout
{
NSAssert(sendTimer == NULL, @"Invalid logic");
NSAssert(timeout >= 0.0, @"Invalid logic");
LogTrace();
sendTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
dispatch_source_set_event_handler(sendTimer, ^{ @autoreleasepool {
[self doSendTimeout];
}});
dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC));
dispatch_source_set_timer(sendTimer, tt, DISPATCH_TIME_FOREVER, 0);
dispatch_resume(sendTimer);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Receiving
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)receiveOnce:(NSError **)errPtr
{
LogTrace();
__block BOOL result = NO;
__block NSError *err = nil;
dispatch_block_t block = ^{
if ((self->flags & kReceiveOnce) == 0)
{
if ((self->flags & kDidCreateSockets) == 0)
{
NSString *msg = @"Must bind socket before you can receive data. "
@"You can do this explicitly via bind, or implicitly via connect or by sending data.";
err = [self badConfigError:msg];
return_from_block;
}
self->flags |= kReceiveOnce; // Enable
self->flags &= ~kReceiveContinuous; // Disable
dispatch_async(self->socketQueue, ^{ @autoreleasepool {
[self doReceive];
}});
}
result = YES;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
if (err)
LogError(@"Error in beginReceiving: %@", err);
if (errPtr)
*errPtr = err;
return result;
}
- (BOOL)beginReceiving:(NSError **)errPtr
{
LogTrace();
__block BOOL result = NO;
__block NSError *err = nil;
dispatch_block_t block = ^{
if ((self->flags & kReceiveContinuous) == 0)
{
if ((self->flags & kDidCreateSockets) == 0)
{
NSString *msg = @"Must bind socket before you can receive data. "
@"You can do this explicitly via bind, or implicitly via connect or by sending data.";
err = [self badConfigError:msg];
return_from_block;
}
self->flags |= kReceiveContinuous; // Enable
self->flags &= ~kReceiveOnce; // Disable
dispatch_async(self->socketQueue, ^{ @autoreleasepool {
[self doReceive];
}});
}
result = YES;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
if (err)
LogError(@"Error in beginReceiving: %@", err);
if (errPtr)
*errPtr = err;
return result;
}
- (void)pauseReceiving
{
LogTrace();
dispatch_block_t block = ^{
self->flags &= ~kReceiveOnce; // Disable
self->flags &= ~kReceiveContinuous; // Disable
if (self->socket4FDBytesAvailable > 0) {
[self suspendReceive4Source];
}
if (self->socket6FDBytesAvailable > 0) {
[self suspendReceive6Source];
}
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
- (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue
{
[self setReceiveFilter:filterBlock withQueue:filterQueue isAsynchronous:YES];
}
- (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock
withQueue:(dispatch_queue_t)filterQueue
isAsynchronous:(BOOL)isAsynchronous
{
GCDAsyncUdpSocketReceiveFilterBlock newFilterBlock = NULL;
dispatch_queue_t newFilterQueue = NULL;
if (filterBlock)
{
NSAssert(filterQueue, @"Must provide a dispatch_queue in which to run the filter block.");
newFilterBlock = [filterBlock copy];
newFilterQueue = filterQueue;
#if !OS_OBJECT_USE_OBJC
dispatch_retain(newFilterQueue);
#endif
}
dispatch_block_t block = ^{
#if !OS_OBJECT_USE_OBJC
if (self->receiveFilterQueue) dispatch_release(self->receiveFilterQueue);
#endif
self->receiveFilterBlock = newFilterBlock;
self->receiveFilterQueue = newFilterQueue;
self->receiveFilterAsync = isAsynchronous;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
- (void)doReceive
{
LogTrace();
if ((flags & (kReceiveOnce | kReceiveContinuous)) == 0)
{
LogVerbose(@"Receiving is paused...");
if (socket4FDBytesAvailable > 0) {
[self suspendReceive4Source];
}
if (socket6FDBytesAvailable > 0) {
[self suspendReceive6Source];
}
return;
}
if ((flags & kReceiveOnce) && (pendingFilterOperations > 0))
{
LogVerbose(@"Receiving is temporarily paused (pending filter operations)...");
if (socket4FDBytesAvailable > 0) {
[self suspendReceive4Source];
}
if (socket6FDBytesAvailable > 0) {
[self suspendReceive6Source];
}
return;
}
if ((socket4FDBytesAvailable == 0) && (socket6FDBytesAvailable == 0))
{
LogVerbose(@"No data available to receive...");
if (socket4FDBytesAvailable == 0) {
[self resumeReceive4Source];
}
if (socket6FDBytesAvailable == 0) {
[self resumeReceive6Source];
}
return;
}
// Figure out if we should receive on socket4 or socket6
BOOL doReceive4;
if (flags & kDidConnect)
{
// Connected socket
doReceive4 = (socket4FD != SOCKET_NULL);
}
else
{
// Non-Connected socket
if (socket4FDBytesAvailable > 0)
{
if (socket6FDBytesAvailable > 0)
{
// Bytes available on socket4 & socket6
doReceive4 = (flags & kFlipFlop) ? YES : NO;
flags ^= kFlipFlop; // flags = flags xor kFlipFlop; (toggle flip flop bit)
}
else {
// Bytes available on socket4, but not socket6
doReceive4 = YES;
}
}
else {
// Bytes available on socket6, but not socket4
doReceive4 = NO;
}
}
// Perform socket IO
ssize_t result = 0;
NSData *data = nil;
NSData *addr4 = nil;
NSData *addr6 = nil;
if (doReceive4)
{
NSAssert(socket4FDBytesAvailable > 0, @"Invalid logic");
LogVerbose(@"Receiving on IPv4");
struct sockaddr_in sockaddr4;
socklen_t sockaddr4len = sizeof(sockaddr4);
// #222: GCD does not necessarily return the size of an entire UDP packet
// from dispatch_source_get_data(), so we must use the maximum packet size.
size_t bufSize = max4ReceiveSize;
void *buf = malloc(bufSize);
result = recvfrom(socket4FD, buf, bufSize, 0, (struct sockaddr *)&sockaddr4, &sockaddr4len);
LogVerbose(@"recvfrom(socket4FD) = %i", (int)result);
if (result > 0)
{
if ((size_t)result >= socket4FDBytesAvailable)
socket4FDBytesAvailable = 0;
else
socket4FDBytesAvailable -= result;
if ((size_t)result != bufSize) {
buf = realloc(buf, result);
}
data = [NSData dataWithBytesNoCopy:buf length:result freeWhenDone:YES];
addr4 = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len];
}
else
{
LogVerbose(@"recvfrom(socket4FD) = %@", [self errnoError]);
socket4FDBytesAvailable = 0;
free(buf);
}
}
else
{
NSAssert(socket6FDBytesAvailable > 0, @"Invalid logic");
LogVerbose(@"Receiving on IPv6");
struct sockaddr_in6 sockaddr6;
socklen_t sockaddr6len = sizeof(sockaddr6);
// #222: GCD does not necessarily return the size of an entire UDP packet
// from dispatch_source_get_data(), so we must use the maximum packet size.
size_t bufSize = max6ReceiveSize;
void *buf = malloc(bufSize);
result = recvfrom(socket6FD, buf, bufSize, 0, (struct sockaddr *)&sockaddr6, &sockaddr6len);
LogVerbose(@"recvfrom(socket6FD) -> %i", (int)result);
if (result > 0)
{
if ((size_t)result >= socket6FDBytesAvailable)
socket6FDBytesAvailable = 0;
else
socket6FDBytesAvailable -= result;
if ((size_t)result != bufSize) {
buf = realloc(buf, result);
}
data = [NSData dataWithBytesNoCopy:buf length:result freeWhenDone:YES];
addr6 = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len];
}
else
{
LogVerbose(@"recvfrom(socket6FD) = %@", [self errnoError]);
socket6FDBytesAvailable = 0;
free(buf);
}
}
BOOL waitingForSocket = NO;
BOOL notifiedDelegate = NO;
BOOL ignored = NO;
NSError *socketError = nil;
if (result == 0)
{
waitingForSocket = YES;
}
else if (result < 0)
{
if (errno == EAGAIN)
waitingForSocket = YES;
else
socketError = [self errnoErrorWithReason:@"Error in recvfrom() function"];
}
else
{
if (flags & kDidConnect)
{
if (addr4 && ![self isConnectedToAddress4:addr4])
ignored = YES;
if (addr6 && ![self isConnectedToAddress6:addr6])
ignored = YES;
}
NSData *addr = (addr4 != nil) ? addr4 : addr6;
if (!ignored)
{
if (receiveFilterBlock && receiveFilterQueue)
{
// Run data through filter, and if approved, notify delegate
__block id filterContext = nil;
__block BOOL allowed = NO;
if (receiveFilterAsync)
{
pendingFilterOperations++;
dispatch_async(receiveFilterQueue, ^{ @autoreleasepool {
allowed = self->receiveFilterBlock(data, addr, &filterContext);
// Transition back to socketQueue to get the current delegate / delegateQueue
dispatch_async(self->socketQueue, ^{ @autoreleasepool {
self->pendingFilterOperations--;
if (allowed)
{
[self notifyDidReceiveData:data fromAddress:addr withFilterContext:filterContext];
}
else
{
LogVerbose(@"received packet silently dropped by receiveFilter");
}
if (self->flags & kReceiveOnce)
{
if (allowed)
{
// The delegate has been notified,
// so our receive once operation has completed.
self->flags &= ~kReceiveOnce;
}
else if (self->pendingFilterOperations == 0)
{
// All pending filter operations have completed,
// and none were allowed through.
// Our receive once operation hasn't completed yet.
[self doReceive];
}
}
}});
}});
}
else // if (!receiveFilterAsync)
{
dispatch_sync(receiveFilterQueue, ^{ @autoreleasepool {
allowed = self->receiveFilterBlock(data, addr, &filterContext);
}});
if (allowed)
{
[self notifyDidReceiveData:data fromAddress:addr withFilterContext:filterContext];
notifiedDelegate = YES;
}
else
{
LogVerbose(@"received packet silently dropped by receiveFilter");
ignored = YES;
}
}
}
else // if (!receiveFilterBlock || !receiveFilterQueue)
{
[self notifyDidReceiveData:data fromAddress:addr withFilterContext:nil];
notifiedDelegate = YES;
}
}
}
if (waitingForSocket)
{
// Wait for a notification of available data.
if (socket4FDBytesAvailable == 0) {
[self resumeReceive4Source];
}
if (socket6FDBytesAvailable == 0) {
[self resumeReceive6Source];
}
}
else if (socketError)
{
[self closeWithError:socketError];
}
else
{
if (flags & kReceiveContinuous)
{
// Continuous receive mode
[self doReceive];
}
else
{
// One-at-a-time receive mode
if (notifiedDelegate)
{
// The delegate has been notified (no set filter).
// So our receive once operation has completed.
flags &= ~kReceiveOnce;
}
else if (ignored)
{
[self doReceive];
}
else
{
// Waiting on asynchronous receive filter...
}
}
}
}
- (void)doReceiveEOF
{
LogTrace();
[self closeWithError:[self socketClosedError]];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Closing
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)closeWithError:(NSError *)error
{
LogVerbose(@"closeWithError: %@", error);
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
if (currentSend) [self endCurrentSend];
[sendQueue removeAllObjects];
// If a socket has been created, we should notify the delegate.
BOOL shouldCallDelegate = (flags & kDidCreateSockets) ? YES : NO;
// Close all sockets, send/receive sources, cfstreams, etc
#if TARGET_OS_IPHONE
[self removeStreamsFromRunLoop];
[self closeReadAndWriteStreams];
#endif
[self closeSockets];
// Clear all flags (config remains as is)
flags = 0;
if (shouldCallDelegate)
{
[self notifyDidCloseWithError:error];
}
}
- (void)close
{
LogTrace();
dispatch_block_t block = ^{ @autoreleasepool {
[self closeWithError:nil];
}};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
}
- (void)closeAfterSending
{
LogTrace();
dispatch_block_t block = ^{ @autoreleasepool {
self->flags |= kCloseAfterSends;
if (self->currentSend == nil && [self->sendQueue count] == 0)
{
[self closeWithError:nil];
}
}};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark CFStream
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if TARGET_OS_IPHONE
static NSThread *listenerThread;
+ (void)ignore:(id)_
{}
+ (void)startListenerThreadIfNeeded
{
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
listenerThread = [[NSThread alloc] initWithTarget:self
selector:@selector(listenerThread:)
object:nil];
[listenerThread start];
});
}
+ (void)listenerThread:(id)unused
{
@autoreleasepool {
[[NSThread currentThread] setName:GCDAsyncUdpSocketThreadName];
LogInfo(@"ListenerThread: Started");
// We can't run the run loop unless it has an associated input source or a timer.
// So we'll just create a timer that will never fire - unless the server runs for a decades.
[NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow]
target:self
selector:@selector(ignore:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] run];
LogInfo(@"ListenerThread: Stopped");
}
}
+ (void)addStreamListener:(GCDAsyncUdpSocket *)asyncUdpSocket
{
LogTrace();
NSAssert([NSThread currentThread] == listenerThread, @"Invoked on wrong thread");
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
if (asyncUdpSocket->readStream4)
CFReadStreamScheduleWithRunLoop(asyncUdpSocket->readStream4, runLoop, kCFRunLoopDefaultMode);
if (asyncUdpSocket->readStream6)
CFReadStreamScheduleWithRunLoop(asyncUdpSocket->readStream6, runLoop, kCFRunLoopDefaultMode);
if (asyncUdpSocket->writeStream4)
CFWriteStreamScheduleWithRunLoop(asyncUdpSocket->writeStream4, runLoop, kCFRunLoopDefaultMode);
if (asyncUdpSocket->writeStream6)
CFWriteStreamScheduleWithRunLoop(asyncUdpSocket->writeStream6, runLoop, kCFRunLoopDefaultMode);
}
+ (void)removeStreamListener:(GCDAsyncUdpSocket *)asyncUdpSocket
{
LogTrace();
NSAssert([NSThread currentThread] == listenerThread, @"Invoked on wrong thread");
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
if (asyncUdpSocket->readStream4)
CFReadStreamUnscheduleFromRunLoop(asyncUdpSocket->readStream4, runLoop, kCFRunLoopDefaultMode);
if (asyncUdpSocket->readStream6)
CFReadStreamUnscheduleFromRunLoop(asyncUdpSocket->readStream6, runLoop, kCFRunLoopDefaultMode);
if (asyncUdpSocket->writeStream4)
CFWriteStreamUnscheduleFromRunLoop(asyncUdpSocket->writeStream4, runLoop, kCFRunLoopDefaultMode);
if (asyncUdpSocket->writeStream6)
CFWriteStreamUnscheduleFromRunLoop(asyncUdpSocket->writeStream6, runLoop, kCFRunLoopDefaultMode);
}
static void CFReadStreamCallback(CFReadStreamRef stream, CFStreamEventType type, void *pInfo)
{
@autoreleasepool {
GCDAsyncUdpSocket *asyncUdpSocket = (__bridge GCDAsyncUdpSocket *)pInfo;
switch(type)
{
case kCFStreamEventOpenCompleted:
{
LogCVerbose(@"CFReadStreamCallback - Open");
break;
}
case kCFStreamEventHasBytesAvailable:
{
LogCVerbose(@"CFReadStreamCallback - HasBytesAvailable");
break;
}
case kCFStreamEventErrorOccurred:
case kCFStreamEventEndEncountered:
{
NSError *error = (__bridge_transfer NSError *)CFReadStreamCopyError(stream);
if (error == nil && type == kCFStreamEventEndEncountered)
{
error = [asyncUdpSocket socketClosedError];
}
dispatch_async(asyncUdpSocket->socketQueue, ^{ @autoreleasepool {
LogCVerbose(@"CFReadStreamCallback - %@",
(type == kCFStreamEventErrorOccurred) ? @"Error" : @"EndEncountered");
if (stream != asyncUdpSocket->readStream4 &&
stream != asyncUdpSocket->readStream6 )
{
LogCVerbose(@"CFReadStreamCallback - Ignored");
return_from_block;
}
[asyncUdpSocket closeWithError:error];
}});
break;
}
default:
{
LogCError(@"CFReadStreamCallback - UnknownType: %i", (int)type);
}
}
}
}
static void CFWriteStreamCallback(CFWriteStreamRef stream, CFStreamEventType type, void *pInfo)
{
@autoreleasepool {
GCDAsyncUdpSocket *asyncUdpSocket = (__bridge GCDAsyncUdpSocket *)pInfo;
switch(type)
{
case kCFStreamEventOpenCompleted:
{
LogCVerbose(@"CFWriteStreamCallback - Open");
break;
}
case kCFStreamEventCanAcceptBytes:
{
LogCVerbose(@"CFWriteStreamCallback - CanAcceptBytes");
break;
}
case kCFStreamEventErrorOccurred:
case kCFStreamEventEndEncountered:
{
NSError *error = (__bridge_transfer NSError *)CFWriteStreamCopyError(stream);
if (error == nil && type == kCFStreamEventEndEncountered)
{
error = [asyncUdpSocket socketClosedError];
}
dispatch_async(asyncUdpSocket->socketQueue, ^{ @autoreleasepool {
LogCVerbose(@"CFWriteStreamCallback - %@",
(type == kCFStreamEventErrorOccurred) ? @"Error" : @"EndEncountered");
if (stream != asyncUdpSocket->writeStream4 &&
stream != asyncUdpSocket->writeStream6 )
{
LogCVerbose(@"CFWriteStreamCallback - Ignored");
return_from_block;
}
[asyncUdpSocket closeWithError:error];
}});
break;
}
default:
{
LogCError(@"CFWriteStreamCallback - UnknownType: %i", (int)type);
}
}
}
}
- (BOOL)createReadAndWriteStreams:(NSError **)errPtr
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
NSError *err = nil;
if (readStream4 || writeStream4 || readStream6 || writeStream6)
{
// Streams already created
return YES;
}
if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL)
{
err = [self otherError:@"Cannot create streams without a file descriptor"];
goto Failed;
}
// Create streams
LogVerbose(@"Creating read and write stream(s)...");
if (socket4FD != SOCKET_NULL)
{
CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socket4FD, &readStream4, &writeStream4);
if (!readStream4 || !writeStream4)
{
err = [self otherError:@"Error in CFStreamCreatePairWithSocket() [IPv4]"];
goto Failed;
}
}
if (socket6FD != SOCKET_NULL)
{
CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socket6FD, &readStream6, &writeStream6);
if (!readStream6 || !writeStream6)
{
err = [self otherError:@"Error in CFStreamCreatePairWithSocket() [IPv6]"];
goto Failed;
}
}
// Ensure the CFStream's don't close our underlying socket
CFReadStreamSetProperty(readStream4, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
CFWriteStreamSetProperty(writeStream4, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
CFReadStreamSetProperty(readStream6, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
CFWriteStreamSetProperty(writeStream6, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
return YES;
Failed:
if (readStream4)
{
CFReadStreamClose(readStream4);
CFRelease(readStream4);
readStream4 = NULL;
}
if (writeStream4)
{
CFWriteStreamClose(writeStream4);
CFRelease(writeStream4);
writeStream4 = NULL;
}
if (readStream6)
{
CFReadStreamClose(readStream6);
CFRelease(readStream6);
readStream6 = NULL;
}
if (writeStream6)
{
CFWriteStreamClose(writeStream6);
CFRelease(writeStream6);
writeStream6 = NULL;
}
if (errPtr)
*errPtr = err;
return NO;
}
- (BOOL)registerForStreamCallbacks:(NSError **)errPtr
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null");
NSError *err = nil;
streamContext.version = 0;
streamContext.info = (__bridge void *)self;
streamContext.retain = nil;
streamContext.release = nil;
streamContext.copyDescription = nil;
CFOptionFlags readStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
CFOptionFlags writeStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
// readStreamEvents |= (kCFStreamEventOpenCompleted | kCFStreamEventHasBytesAvailable);
// writeStreamEvents |= (kCFStreamEventOpenCompleted | kCFStreamEventCanAcceptBytes);
if (socket4FD != SOCKET_NULL)
{
if (readStream4 == NULL || writeStream4 == NULL)
{
err = [self otherError:@"Read/Write stream4 is null"];
goto Failed;
}
BOOL r1 = CFReadStreamSetClient(readStream4, readStreamEvents, &CFReadStreamCallback, &streamContext);
BOOL r2 = CFWriteStreamSetClient(writeStream4, writeStreamEvents, &CFWriteStreamCallback, &streamContext);
if (!r1 || !r2)
{
err = [self otherError:@"Error in CFStreamSetClient(), [IPv4]"];
goto Failed;
}
}
if (socket6FD != SOCKET_NULL)
{
if (readStream6 == NULL || writeStream6 == NULL)
{
err = [self otherError:@"Read/Write stream6 is null"];
goto Failed;
}
BOOL r1 = CFReadStreamSetClient(readStream6, readStreamEvents, &CFReadStreamCallback, &streamContext);
BOOL r2 = CFWriteStreamSetClient(writeStream6, writeStreamEvents, &CFWriteStreamCallback, &streamContext);
if (!r1 || !r2)
{
err = [self otherError:@"Error in CFStreamSetClient() [IPv6]"];
goto Failed;
}
}
return YES;
Failed:
if (readStream4) {
CFReadStreamSetClient(readStream4, kCFStreamEventNone, NULL, NULL);
}
if (writeStream4) {
CFWriteStreamSetClient(writeStream4, kCFStreamEventNone, NULL, NULL);
}
if (readStream6) {
CFReadStreamSetClient(readStream6, kCFStreamEventNone, NULL, NULL);
}
if (writeStream6) {
CFWriteStreamSetClient(writeStream6, kCFStreamEventNone, NULL, NULL);
}
if (errPtr) *errPtr = err;
return NO;
}
- (BOOL)addStreamsToRunLoop:(NSError **)errPtr
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null");
if (!(flags & kAddedStreamListener))
{
[[self class] startListenerThreadIfNeeded];
[[self class] performSelector:@selector(addStreamListener:)
onThread:listenerThread
withObject:self
waitUntilDone:YES];
flags |= kAddedStreamListener;
}
return YES;
}
- (BOOL)openStreams:(NSError **)errPtr
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null");
NSError *err = nil;
if (socket4FD != SOCKET_NULL)
{
BOOL r1 = CFReadStreamOpen(readStream4);
BOOL r2 = CFWriteStreamOpen(writeStream4);
if (!r1 || !r2)
{
err = [self otherError:@"Error in CFStreamOpen() [IPv4]"];
goto Failed;
}
}
if (socket6FD != SOCKET_NULL)
{
BOOL r1 = CFReadStreamOpen(readStream6);
BOOL r2 = CFWriteStreamOpen(writeStream6);
if (!r1 || !r2)
{
err = [self otherError:@"Error in CFStreamOpen() [IPv6]"];
goto Failed;
}
}
return YES;
Failed:
if (errPtr) *errPtr = err;
return NO;
}
- (void)removeStreamsFromRunLoop
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
if (flags & kAddedStreamListener)
{
[[self class] performSelector:@selector(removeStreamListener:)
onThread:listenerThread
withObject:self
waitUntilDone:YES];
flags &= ~kAddedStreamListener;
}
}
- (void)closeReadAndWriteStreams
{
LogTrace();
if (readStream4)
{
CFReadStreamSetClient(readStream4, kCFStreamEventNone, NULL, NULL);
CFReadStreamClose(readStream4);
CFRelease(readStream4);
readStream4 = NULL;
}
if (writeStream4)
{
CFWriteStreamSetClient(writeStream4, kCFStreamEventNone, NULL, NULL);
CFWriteStreamClose(writeStream4);
CFRelease(writeStream4);
writeStream4 = NULL;
}
if (readStream6)
{
CFReadStreamSetClient(readStream6, kCFStreamEventNone, NULL, NULL);
CFReadStreamClose(readStream6);
CFRelease(readStream6);
readStream6 = NULL;
}
if (writeStream6)
{
CFWriteStreamSetClient(writeStream6, kCFStreamEventNone, NULL, NULL);
CFWriteStreamClose(writeStream6);
CFRelease(writeStream6);
writeStream6 = NULL;
}
}
#endif
#if TARGET_OS_IPHONE
- (void)applicationWillEnterForeground:(NSNotification *)notification
{
LogTrace();
// If the application was backgrounded, then iOS may have shut down our sockets.
// So we take a quick look to see if any of them received an EOF.
dispatch_block_t block = ^{ @autoreleasepool {
[self resumeReceive4Source];
[self resumeReceive6Source];
}};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Advanced
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* See header file for big discussion of this method.
**/
- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketNewTargetQueue
{
void *nonNullUnusedPointer = (__bridge void *)self;
dispatch_queue_set_specific(socketNewTargetQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL);
}
/**
* See header file for big discussion of this method.
**/
- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketOldTargetQueue
{
dispatch_queue_set_specific(socketOldTargetQueue, IsOnSocketQueueOrTargetQueueKey, NULL, NULL);
}
- (void)performBlock:(dispatch_block_t)block
{
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
}
- (int)socketFD
{
if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
THIS_FILE, THIS_METHOD);
return SOCKET_NULL;
}
if (socket4FD != SOCKET_NULL)
return socket4FD;
else
return socket6FD;
}
- (int)socket4FD
{
if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
THIS_FILE, THIS_METHOD);
return SOCKET_NULL;
}
return socket4FD;
}
- (int)socket6FD
{
if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
THIS_FILE, THIS_METHOD);
return SOCKET_NULL;
}
return socket6FD;
}
#if TARGET_OS_IPHONE
- (CFReadStreamRef)readStream
{
if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
THIS_FILE, THIS_METHOD);
return NULL;
}
NSError *err = nil;
if (![self createReadAndWriteStreams:&err])
{
LogError(@"Error creating CFStream(s): %@", err);
return NULL;
}
// Todo...
if (readStream4)
return readStream4;
else
return readStream6;
}
- (CFWriteStreamRef)writeStream
{
if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
THIS_FILE, THIS_METHOD);
return NULL;
}
NSError *err = nil;
if (![self createReadAndWriteStreams:&err])
{
LogError(@"Error creating CFStream(s): %@", err);
return NULL;
}
if (writeStream4)
return writeStream4;
else
return writeStream6;
}
- (BOOL)enableBackgroundingOnSockets
{
if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
THIS_FILE, THIS_METHOD);
return NO;
}
// Why is this commented out?
// See comments below.
// NSError *err = nil;
// if (![self createReadAndWriteStreams:&err])
// {
// LogError(@"Error creating CFStream(s): %@", err);
// return NO;
// }
//
// LogVerbose(@"Enabling backgrouding on socket");
//
// BOOL r1, r2;
//
// if (readStream4 && writeStream4)
// {
// r1 = CFReadStreamSetProperty(readStream4, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
// r2 = CFWriteStreamSetProperty(writeStream4, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
//
// if (!r1 || !r2)
// {
// LogError(@"Error setting voip type (IPv4)");
// return NO;
// }
// }
//
// if (readStream6 && writeStream6)
// {
// r1 = CFReadStreamSetProperty(readStream6, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
// r2 = CFWriteStreamSetProperty(writeStream6, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
//
// if (!r1 || !r2)
// {
// LogError(@"Error setting voip type (IPv6)");
// return NO;
// }
// }
//
// return YES;
// The above code will actually appear to work.
// The methods will return YES, and everything will appear fine.
//
// One tiny problem: the sockets will still get closed when the app gets backgrounded.
//
// Apple does not officially support backgrounding UDP sockets.
return NO;
}
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Class Methods
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4
{
char addrBuf[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET, &pSockaddr4->sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
{
addrBuf[0] = '\0';
}
return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
}
+ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6
{
char addrBuf[INET6_ADDRSTRLEN];
if (inet_ntop(AF_INET6, &pSockaddr6->sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
{
addrBuf[0] = '\0';
}
return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
}
+ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4
{
return ntohs(pSockaddr4->sin_port);
}
+ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6
{
return ntohs(pSockaddr6->sin6_port);
}
+ (NSString *)hostFromAddress:(NSData *)address
{
NSString *host = nil;
[self getHost:&host port:NULL family:NULL fromAddress:address];
return host;
}
+ (uint16_t)portFromAddress:(NSData *)address
{
uint16_t port = 0;
[self getHost:NULL port:&port family:NULL fromAddress:address];
return port;
}
+ (int)familyFromAddress:(NSData *)address
{
int af = AF_UNSPEC;
[self getHost:NULL port:NULL family:&af fromAddress:address];
return af;
}
+ (BOOL)isIPv4Address:(NSData *)address
{
int af = AF_UNSPEC;
[self getHost:NULL port:NULL family:&af fromAddress:address];
return (af == AF_INET);
}
+ (BOOL)isIPv6Address:(NSData *)address
{
int af = AF_UNSPEC;
[self getHost:NULL port:NULL family:&af fromAddress:address];
return (af == AF_INET6);
}
+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address
{
return [self getHost:hostPtr port:portPtr family:NULL fromAddress:address];
}
+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr family:(int *)afPtr fromAddress:(NSData *)address
{
if ([address length] >= sizeof(struct sockaddr))
{
const struct sockaddr *addrX = (const struct sockaddr *)[address bytes];
if (addrX->sa_family == AF_INET)
{
if ([address length] >= sizeof(struct sockaddr_in))
{
const struct sockaddr_in *addr4 = (const struct sockaddr_in *)(const void *)addrX;
if (hostPtr) *hostPtr = [self hostFromSockaddr4:addr4];
if (portPtr) *portPtr = [self portFromSockaddr4:addr4];
if (afPtr) *afPtr = AF_INET;
return YES;
}
}
else if (addrX->sa_family == AF_INET6)
{
if ([address length] >= sizeof(struct sockaddr_in6))
{
const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)(const void *)addrX;
if (hostPtr) *hostPtr = [self hostFromSockaddr6:addr6];
if (portPtr) *portPtr = [self portFromSockaddr6:addr6];
if (afPtr) *afPtr = AF_INET6;
return YES;
}
}
}
if (hostPtr) *hostPtr = nil;
if (portPtr) *portPtr = 0;
if (afPtr) *afPtr = AF_UNSPEC;
return NO;
}
@end
================================================
FILE: Pods/DZNEmptyDataSet/LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2016 Ignacio Romero Zurbuchen iromero@dzen.cl
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.
================================================
FILE: Pods/DZNEmptyDataSet/README.md
================================================
DZNEmptyDataSet
=================
[](http://cocoadocs.org/docsets/DZNEmptyDataSet/)
[](https://github.com/Carthage/Carthage)
[](http://opensource.org/licenses/MIT)
### Projects using this library
[Add your project to the list here](https://github.com/dzenbot/DZNEmptyDataSet/wiki/Projects-using-DZNEmptyDataSet) and provide a (320px wide) render of the result.
### The Empty Data Set Pattern
Also known as *[Empty State](http://emptystat.es/)* or *[Blank Slate](http://patternry.com/p=blank-slate/)*.
Most applications show lists of content (data sets), which many turn out to be empty at one point, specially for new users with blank accounts. Empty screens create confusion by not being clear about what's going on, if there is an error/bug or if the user is supposed to do something within your app to be able to consume the content.
Please read this very interesting article about [*Designing For The Empty States*](http://tympanus.net/codrops/2013/01/09/designing-for-the-empty-states/).


(*These are real life examples, available in the 'Applications' sample project*)
**[Empty Data Sets](http://pttrns.com/?did=1&scid=30)** are helpful for:
* Avoiding white-screens and communicating to your users why the screen is empty.
* Calling to action (particularly as an onboarding process).
* Avoiding other interruptive mechanisms like showing error alerts.
* Being consistent and improving the user experience.
* Delivering a brand presence.
### Features
* Compatible with UITableView and UICollectionView. Also compatible with UISearchDisplayController and UIScrollView.
* Gives multiple possibilities of layout and appearance, by showing an image and/or title label and/or description label and/or button.
* Uses NSAttributedString for easier appearance customisation.
* Uses auto-layout to automagically center the content to the tableview, with auto-rotation support. Also accepts custom vertical and horizontal alignment.
* Background color customisation.
* Allows tap gesture on the whole tableview rectangle (useful for resigning first responder or similar actions).
* For more advanced customisation, it allows a custom view.
* Compatible with Storyboard.
* Compatible with iOS 6 or later.
* Compatible with iPhone and iPad.
* **App Store ready**
This library has been designed in a way where you won't need to extend UITableView or UICollectionView class. It will still work when using UITableViewController or UICollectionViewController.
By just conforming to DZNEmptyDataSetSource & DZNEmptyDataSetDelegate, you will be able to fully customize the content and appearance of the empty states for your application.
## Installation
Available in [CocoaPods](http://cocoapods.org/?q=DZNEmptyDataSet)
```ruby
pod 'DZNEmptyDataSet'
```
To integrate DZNEmptyDataSet into your Xcode project using Carthage, specify it in your `Cartfile`:
```ruby
github "dzenbot/DZNEmptyDataSet"
```
## How to use
For complete documentation, [visit CocoaPods' auto-generated doc](http://cocoadocs.org/docsets/DZNEmptyDataSet/)
### Import
```objc
#import "UIScrollView+EmptyDataSet.h"
```
Unless you are importing as a framework, then do:
```objc
#import ""
```
### Protocol Conformance
Conform to datasource and/or delegate.
```objc
@interface MainViewController : UITableViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableView.emptyDataSetSource = self;
self.tableView.emptyDataSetDelegate = self;
// A little trick for removing the cell separators
self.tableView.tableFooterView = [UIView new];
}
```
### Data Source Implementation
Return the content you want to show on the empty state, and take advantage of NSAttributedString features to customise the text appearance.
The image for the empty state:
```objc
- (UIImage *)imageForEmptyDataSet:(UIScrollView *)scrollView
{
return [UIImage imageNamed:@"empty_placeholder"];
}
```
The image view animation
```objc
- (CAAnimation *)imageAnimationForEmptyDataSet:(UIScrollView *)scrollView
{
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath: @"transform"];
animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI_2, 0.0, 0.0, 1.0)];
animation.duration = 0.25;
animation.cumulative = YES;
animation.repeatCount = MAXFLOAT;
return animation;
}
```
The attributed string for the title of the empty state:
```objc
- (NSAttributedString *)titleForEmptyDataSet:(UIScrollView *)scrollView
{
NSString *text = @"Please Allow Photo Access";
NSDictionary *attributes = @{NSFontAttributeName: [UIFont boldSystemFontOfSize:18.0f],
NSForegroundColorAttributeName: [UIColor darkGrayColor]};
return [[NSAttributedString alloc] initWithString:text attributes:attributes];
}
```
The attributed string for the description of the empty state:
```objc
- (NSAttributedString *)descriptionForEmptyDataSet:(UIScrollView *)scrollView
{
NSString *text = @"This allows you to share photos from your library and save photos to your camera roll.";
NSMutableParagraphStyle *paragraph = [NSMutableParagraphStyle new];
paragraph.lineBreakMode = NSLineBreakByWordWrapping;
paragraph.alignment = NSTextAlignmentCenter;
NSDictionary *attributes = @{NSFontAttributeName: [UIFont systemFontOfSize:14.0f],
NSForegroundColorAttributeName: [UIColor lightGrayColor],
NSParagraphStyleAttributeName: paragraph};
return [[NSAttributedString alloc] initWithString:text attributes:attributes];
}
```
The attributed string to be used for the specified button state:
```objc
- (NSAttributedString *)buttonTitleForEmptyDataSet:(UIScrollView *)scrollView forState:(UIControlState)state
{
NSDictionary *attributes = @{NSFontAttributeName: [UIFont boldSystemFontOfSize:17.0f]};
return [[NSAttributedString alloc] initWithString:@"Continue" attributes:attributes];
}
```
or the image to be used for the specified button state:
```objc
- (UIImage *)buttonImageForEmptyDataSet:(UIScrollView *)scrollView forState:(UIControlState)state
{
return [UIImage imageNamed:@"button_image"];
}
```
The background color for the empty state:
```objc
- (UIColor *)backgroundColorForEmptyDataSet:(UIScrollView *)scrollView
{
return [UIColor whiteColor];
}
```
If you need a more complex layout, you can return a custom view instead:
```objc
- (UIView *)customViewForEmptyDataSet:(UIScrollView *)scrollView
{
UIActivityIndicatorView *activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[activityView startAnimating];
return activityView;
}
```
Additionally, you can also adjust the vertical alignment of the content view (ie: useful when there is tableHeaderView visible):
```objc
- (CGFloat)verticalOffsetForEmptyDataSet:(UIScrollView *)scrollView
{
return -self.tableView.tableHeaderView.frame.size.height/2.0f;
}
```
Finally, you can separate components from each other (default separation is 11 pts):
```objc
- (CGFloat)spaceHeightForEmptyDataSet:(UIScrollView *)scrollView
{
return 20.0f;
}
```
### Delegate Implementation
Return the behaviours you would expect from the empty states, and receive the user events.
Asks to know if the empty state should be rendered and displayed (Default is YES) :
```objc
- (BOOL)emptyDataSetShouldDisplay:(UIScrollView *)scrollView
{
return YES;
}
```
Asks for interaction permission (Default is YES) :
```objc
- (BOOL)emptyDataSetShouldAllowTouch:(UIScrollView *)scrollView
{
return YES;
}
```
Asks for scrolling permission (Default is NO) :
```objc
- (BOOL)emptyDataSetShouldAllowScroll:(UIScrollView *)scrollView
{
return YES;
}
```
Asks for image view animation permission (Default is NO) :
```objc
- (BOOL) emptyDataSetShouldAllowImageViewAnimate:(UIScrollView *)scrollView
{
return YES;
}
```
Notifies when the dataset view was tapped:
```objc
- (void)emptyDataSet:(UIScrollView *)scrollView didTapView:(UIView *)view
{
// Do something
}
```
Notifies when the data set call to action button was tapped:
```objc
- (void)emptyDataSet:(UIScrollView *)scrollView didTapButton:(UIButton *)button
{
// Do something
}
```
### Refresh layout
If you need to refresh the empty state layout, simply call:
```objc
[self.tableView reloadData];
```
or
```objc
[self.collectionView reloadData];
```
depending of which you are using.
### Force layout update
You can also call `[self.tableView reloadEmptyDataSet]` to invalidate the current empty state layout and trigger a layout update, bypassing `-reloadData`. This might be useful if you have a lot of logic on your data source that you want to avoid calling, when not needed. `[self.scrollView reloadEmptyDataSet]` is the only way to refresh content when using with UIScrollView.
## Sample projects
#### Applications
This project replicates several popular application's empty states (~20) with their exact content and appearance, such as Airbnb, Dropbox, Facebook, Foursquare, and many others. See how easy and flexible it is to customize the appearance of your empty states. You can also use this project as a playground to test things.
#### Countries
This project shows a list of the world countries loaded from CoreData. It uses NSFecthedResultController for filtering search. When searching and no content is matched, a simple empty state is shown. See how to interact between the UITableViewDataSource and the DZNEmptyDataSetSource protocols, while using a typical CoreData stack.
#### Colors
This project is a simple example of how this library also works with UICollectionView and UISearchDisplayController, while using Storyboards.
## Collaboration
Feel free to collaborate with ideas, issues and/or pull requests.
## License
(The MIT License)
Copyright (c) 2016 Ignacio Romero Zurbuchen iromero@dzen.cl
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.
================================================
FILE: Pods/DZNEmptyDataSet/Source/UIScrollView+EmptyDataSet.h
================================================
//
// UIScrollView+EmptyDataSet.h
// DZNEmptyDataSet
// https://github.com/dzenbot/DZNEmptyDataSet
//
// Created by Ignacio Romero Zurbuchen on 6/20/14.
// Copyright (c) 2016 DZN Labs. All rights reserved.
// Licence: MIT-Licence
//
#import
@protocol DZNEmptyDataSetSource;
@protocol DZNEmptyDataSetDelegate;
#define DZNEmptyDataSetDeprecated(instead) DEPRECATED_MSG_ATTRIBUTE(" Use " # instead " instead")
/**
A drop-in UITableView/UICollectionView superclass category for showing empty datasets whenever the view has no content to display.
@discussion It will work automatically, by just conforming to DZNEmptyDataSetSource, and returning the data you want to show.
*/
@interface UIScrollView (EmptyDataSet)
/** The empty datasets data source. */
@property (nonatomic, weak) IBOutlet id emptyDataSetSource;
/** The empty datasets delegate. */
@property (nonatomic, weak) IBOutlet id emptyDataSetDelegate;
/** YES if any empty dataset is visible. */
@property (nonatomic, readonly, getter = isEmptyDataSetVisible) BOOL emptyDataSetVisible;
/**
Reloads the empty dataset content receiver.
@discussion Call this method to force all the data to refresh. Calling -reloadData is similar, but this forces only the empty dataset to reload, not the entire table view or collection view.
*/
- (void)reloadEmptyDataSet;
@end
/**
The object that acts as the data source of the empty datasets.
@discussion The data source must adopt the DZNEmptyDataSetSource protocol. The data source is not retained. All data source methods are optional.
*/
@protocol DZNEmptyDataSetSource
@optional
/**
Asks the data source for the title of the dataset.
The dataset uses a fixed font style by default, if no attributes are set. If you want a different font style, return a attributed string.
@param scrollView A scrollView subclass informing the data source.
@return An attributed string for the dataset title, combining font, text color, text pararaph style, etc.
*/
- (NSAttributedString *)titleForEmptyDataSet:(UIScrollView *)scrollView;
/**
Asks the data source for the description of the dataset.
The dataset uses a fixed font style by default, if no attributes are set. If you want a different font style, return a attributed string.
@param scrollView A scrollView subclass informing the data source.
@return An attributed string for the dataset description text, combining font, text color, text pararaph style, etc.
*/
- (NSAttributedString *)descriptionForEmptyDataSet:(UIScrollView *)scrollView;
/**
Asks the data source for the image of the dataset.
@param scrollView A scrollView subclass informing the data source.
@return An image for the dataset.
*/
- (UIImage *)imageForEmptyDataSet:(UIScrollView *)scrollView;
/**
Asks the data source for a tint color of the image dataset. Default is nil.
@param scrollView A scrollView subclass object informing the data source.
@return A color to tint the image of the dataset.
*/
- (UIColor *)imageTintColorForEmptyDataSet:(UIScrollView *)scrollView;
/**
* Asks the data source for the image animation of the dataset.
*
* @param scrollView A scrollView subclass object informing the delegate.
*
* @return image animation
*/
- (CAAnimation *) imageAnimationForEmptyDataSet:(UIScrollView *) scrollView;
/**
Asks the data source for the title to be used for the specified button state.
The dataset uses a fixed font style by default, if no attributes are set. If you want a different font style, return a attributed string.
@param scrollView A scrollView subclass object informing the data source.
@param state The state that uses the specified title. The possible values are described in UIControlState.
@return An attributed string for the dataset button title, combining font, text color, text pararaph style, etc.
*/
- (NSAttributedString *)buttonTitleForEmptyDataSet:(UIScrollView *)scrollView forState:(UIControlState)state;
/**
Asks the data source for the image to be used for the specified button state.
This method will override buttonTitleForEmptyDataSet:forState: and present the image only without any text.
@param scrollView A scrollView subclass object informing the data source.
@param state The state that uses the specified title. The possible values are described in UIControlState.
@return An image for the dataset button imageview.
*/
- (UIImage *)buttonImageForEmptyDataSet:(UIScrollView *)scrollView forState:(UIControlState)state;
/**
Asks the data source for a background image to be used for the specified button state.
There is no default style for this call.
@param scrollView A scrollView subclass informing the data source.
@param state The state that uses the specified image. The values are described in UIControlState.
@return An attributed string for the dataset button title, combining font, text color, text pararaph style, etc.
*/
- (UIImage *)buttonBackgroundImageForEmptyDataSet:(UIScrollView *)scrollView forState:(UIControlState)state;
/**
Asks the data source for the background color of the dataset. Default is clear color.
@param scrollView A scrollView subclass object informing the data source.
@return A color to be applied to the dataset background view.
*/
- (UIColor *)backgroundColorForEmptyDataSet:(UIScrollView *)scrollView;
/**
Asks the data source for a custom view to be displayed instead of the default views such as labels, imageview and button. Default is nil.
Use this method to show an activity view indicator for loading feedback, or for complete custom empty data set.
Returning a custom view will ignore -offsetForEmptyDataSet and -spaceHeightForEmptyDataSet configurations.
@param scrollView A scrollView subclass object informing the delegate.
@return The custom view.
*/
- (UIView *)customViewForEmptyDataSet:(UIScrollView *)scrollView;
/**
Asks the data source for a offset for vertical and horizontal alignment of the content. Default is CGPointZero.
@param scrollView A scrollView subclass object informing the delegate.
@return The offset for vertical and horizontal alignment.
*/
- (CGPoint)offsetForEmptyDataSet:(UIScrollView *)scrollView DZNEmptyDataSetDeprecated(-verticalOffsetForEmptyDataSet:);
- (CGFloat)verticalOffsetForEmptyDataSet:(UIScrollView *)scrollView;
/**
Asks the data source for a vertical space between elements. Default is 11 pts.
@param scrollView A scrollView subclass object informing the delegate.
@return The space height between elements.
*/
- (CGFloat)spaceHeightForEmptyDataSet:(UIScrollView *)scrollView;
@end
/**
The object that acts as the delegate of the empty datasets.
@discussion The delegate can adopt the DZNEmptyDataSetDelegate protocol. The delegate is not retained. All delegate methods are optional.
@discussion All delegate methods are optional. Use this delegate for receiving action callbacks.
*/
@protocol DZNEmptyDataSetDelegate
@optional
/**
Asks the delegate to know if the empty dataset should fade in when displayed. Default is YES.
@param scrollView A scrollView subclass object informing the delegate.
@return YES if the empty dataset should fade in.
*/
- (BOOL)emptyDataSetShouldFadeIn:(UIScrollView *)scrollView;
/**
Asks the delegate to know if the empty dataset should still be displayed when the amount of items is more than 0. Default is NO
@param scrollView A scrollView subclass object informing the delegate.
@return YES if empty dataset should be forced to display
*/
- (BOOL)emptyDataSetShouldBeForcedToDisplay:(UIScrollView *)scrollView;
/**
Asks the delegate to know if the empty dataset should be rendered and displayed. Default is YES.
@param scrollView A scrollView subclass object informing the delegate.
@return YES if the empty dataset should show.
*/
- (BOOL)emptyDataSetShouldDisplay:(UIScrollView *)scrollView;
/**
Asks the delegate for touch permission. Default is YES.
@param scrollView A scrollView subclass object informing the delegate.
@return YES if the empty dataset receives touch gestures.
*/
- (BOOL)emptyDataSetShouldAllowTouch:(UIScrollView *)scrollView;
/**
Asks the delegate for scroll permission. Default is NO.
@param scrollView A scrollView subclass object informing the delegate.
@return YES if the empty dataset is allowed to be scrollable.
*/
- (BOOL)emptyDataSetShouldAllowScroll:(UIScrollView *)scrollView;
/**
Asks the delegate for image view animation permission. Default is NO.
Make sure to return a valid CAAnimation object from imageAnimationForEmptyDataSet:
@param scrollView A scrollView subclass object informing the delegate.
@return YES if the empty dataset is allowed to animate
*/
- (BOOL)emptyDataSetShouldAnimateImageView:(UIScrollView *)scrollView;
/**
Tells the delegate that the empty dataset view was tapped.
Use this method either to resignFirstResponder of a textfield or searchBar.
@param scrollView A scrollView subclass informing the delegate.
*/
- (void)emptyDataSetDidTapView:(UIScrollView *)scrollView DZNEmptyDataSetDeprecated(-emptyDataSet:didTapView:);
/**
Tells the delegate that the action button was tapped.
@param scrollView A scrollView subclass informing the delegate.
*/
- (void)emptyDataSetDidTapButton:(UIScrollView *)scrollView DZNEmptyDataSetDeprecated(-emptyDataSet:didTapButton:);
/**
Tells the delegate that the empty dataset view was tapped.
Use this method either to resignFirstResponder of a textfield or searchBar.
@param scrollView A scrollView subclass informing the delegate.
@param view the view tapped by the user
*/
- (void)emptyDataSet:(UIScrollView *)scrollView didTapView:(UIView *)view;
/**
Tells the delegate that the action button was tapped.
@param scrollView A scrollView subclass informing the delegate.
@param button the button tapped by the user
*/
- (void)emptyDataSet:(UIScrollView *)scrollView didTapButton:(UIButton *)button;
/**
Tells the delegate that the empty data set will appear.
@param scrollView A scrollView subclass informing the delegate.
*/
- (void)emptyDataSetWillAppear:(UIScrollView *)scrollView;
/**
Tells the delegate that the empty data set did appear.
@param scrollView A scrollView subclass informing the delegate.
*/
- (void)emptyDataSetDidAppear:(UIScrollView *)scrollView;
/**
Tells the delegate that the empty data set will disappear.
@param scrollView A scrollView subclass informing the delegate.
*/
- (void)emptyDataSetWillDisappear:(UIScrollView *)scrollView;
/**
Tells the delegate that the empty data set did disappear.
@param scrollView A scrollView subclass informing the delegate.
*/
- (void)emptyDataSetDidDisappear:(UIScrollView *)scrollView;
@end
#undef DZNEmptyDataSetDeprecated
================================================
FILE: Pods/DZNEmptyDataSet/Source/UIScrollView+EmptyDataSet.m
================================================
//
// UIScrollView+EmptyDataSet.m
// DZNEmptyDataSet
// https://github.com/dzenbot/DZNEmptyDataSet
//
// Created by Ignacio Romero Zurbuchen on 6/20/14.
// Copyright (c) 2016 DZN Labs. All rights reserved.
// Licence: MIT-Licence
//
#import "UIScrollView+EmptyDataSet.h"
#import
@interface UIView (DZNConstraintBasedLayoutExtensions)
- (NSLayoutConstraint *)equallyRelatedConstraintWithView:(UIView *)view attribute:(NSLayoutAttribute)attribute;
@end
@interface DZNWeakObjectContainer : NSObject
@property (nonatomic, readonly, weak) id weakObject;
- (instancetype)initWithWeakObject:(id)object;
@end
@interface DZNEmptyDataSetView : UIView
@property (nonatomic, readonly) UIView *contentView;
@property (nonatomic, readonly) UILabel *titleLabel;
@property (nonatomic, readonly) UILabel *detailLabel;
@property (nonatomic, readonly) UIImageView *imageView;
@property (nonatomic, readonly) UIButton *button;
@property (nonatomic, strong) UIView *customView;
@property (nonatomic, strong) UITapGestureRecognizer *tapGesture;
@property (nonatomic, assign) CGFloat verticalOffset;
@property (nonatomic, assign) CGFloat verticalSpace;
@property (nonatomic, assign) BOOL fadeInOnDisplay;
- (void)setupConstraints;
- (void)prepareForReuse;
@end
#pragma mark - UIScrollView+EmptyDataSet
static char const * const kEmptyDataSetSource = "emptyDataSetSource";
static char const * const kEmptyDataSetDelegate = "emptyDataSetDelegate";
static char const * const kEmptyDataSetView = "emptyDataSetView";
#define kEmptyImageViewAnimationKey @"com.dzn.emptyDataSet.imageViewAnimation"
@interface UIScrollView ()
@property (nonatomic, readonly) DZNEmptyDataSetView *emptyDataSetView;
@end
@implementation UIScrollView (DZNEmptyDataSet)
#pragma mark - Getters (Public)
- (id)emptyDataSetSource
{
DZNWeakObjectContainer *container = objc_getAssociatedObject(self, kEmptyDataSetSource);
return container.weakObject;
}
- (id)emptyDataSetDelegate
{
DZNWeakObjectContainer *container = objc_getAssociatedObject(self, kEmptyDataSetDelegate);
return container.weakObject;
}
- (BOOL)isEmptyDataSetVisible
{
UIView *view = objc_getAssociatedObject(self, kEmptyDataSetView);
return view ? !view.hidden : NO;
}
#pragma mark - Getters (Private)
- (DZNEmptyDataSetView *)emptyDataSetView
{
DZNEmptyDataSetView *view = objc_getAssociatedObject(self, kEmptyDataSetView);
if (!view)
{
view = [DZNEmptyDataSetView new];
view.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
view.hidden = YES;
view.tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dzn_didTapContentView:)];
view.tapGesture.delegate = self;
[view addGestureRecognizer:view.tapGesture];
[self setEmptyDataSetView:view];
}
return view;
}
- (BOOL)dzn_canDisplay
{
if (self.emptyDataSetSource && [self.emptyDataSetSource conformsToProtocol:@protocol(DZNEmptyDataSetSource)]) {
if ([self isKindOfClass:[UITableView class]] || [self isKindOfClass:[UICollectionView class]] || [self isKindOfClass:[UIScrollView class]]) {
return YES;
}
}
return NO;
}
- (NSInteger)dzn_itemsCount
{
NSInteger items = 0;
// UIScollView doesn't respond to 'dataSource' so let's exit
if (![self respondsToSelector:@selector(dataSource)]) {
return items;
}
// UITableView support
if ([self isKindOfClass:[UITableView class]]) {
UITableView *tableView = (UITableView *)self;
id dataSource = tableView.dataSource;
NSInteger sections = 1;
if (dataSource && [dataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) {
sections = [dataSource numberOfSectionsInTableView:tableView];
}
if (dataSource && [dataSource respondsToSelector:@selector(tableView:numberOfRowsInSection:)]) {
for (NSInteger section = 0; section < sections; section++) {
items += [dataSource tableView:tableView numberOfRowsInSection:section];
}
}
}
// UICollectionView support
else if ([self isKindOfClass:[UICollectionView class]]) {
UICollectionView *collectionView = (UICollectionView *)self;
id dataSource = collectionView.dataSource;
NSInteger sections = 1;
if (dataSource && [dataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]) {
sections = [dataSource numberOfSectionsInCollectionView:collectionView];
}
if (dataSource && [dataSource respondsToSelector:@selector(collectionView:numberOfItemsInSection:)]) {
for (NSInteger section = 0; section < sections; section++) {
items += [dataSource collectionView:collectionView numberOfItemsInSection:section];
}
}
}
return items;
}
#pragma mark - Data Source Getters
- (NSAttributedString *)dzn_titleLabelString
{
if (self.emptyDataSetSource && [self.emptyDataSetSource respondsToSelector:@selector(titleForEmptyDataSet:)]) {
NSAttributedString *string = [self.emptyDataSetSource titleForEmptyDataSet:self];
if (string) NSAssert([string isKindOfClass:[NSAttributedString class]], @"You must return a valid NSAttributedString object for -titleForEmptyDataSet:");
return string;
}
return nil;
}
- (NSAttributedString *)dzn_detailLabelString
{
if (self.emptyDataSetSource && [self.emptyDataSetSource respondsToSelector:@selector(descriptionForEmptyDataSet:)]) {
NSAttributedString *string = [self.emptyDataSetSource descriptionForEmptyDataSet:self];
if (string) NSAssert([string isKindOfClass:[NSAttributedString class]], @"You must return a valid NSAttributedString object for -descriptionForEmptyDataSet:");
return string;
}
return nil;
}
- (UIImage *)dzn_image
{
if (self.emptyDataSetSource && [self.emptyDataSetSource respondsToSelector:@selector(imageForEmptyDataSet:)]) {
UIImage *image = [self.emptyDataSetSource imageForEmptyDataSet:self];
if (image) NSAssert([image isKindOfClass:[UIImage class]], @"You must return a valid UIImage object for -imageForEmptyDataSet:");
return image;
}
return nil;
}
- (CAAnimation *)dzn_imageAnimation
{
if (self.emptyDataSetSource && [self.emptyDataSetSource respondsToSelector:@selector(imageAnimationForEmptyDataSet:)]) {
CAAnimation *imageAnimation = [self.emptyDataSetSource imageAnimationForEmptyDataSet:self];
if (imageAnimation) NSAssert([imageAnimation isKindOfClass:[CAAnimation class]], @"You must return a valid CAAnimation object for -imageAnimationForEmptyDataSet:");
return imageAnimation;
}
return nil;
}
- (UIColor *)dzn_imageTintColor
{
if (self.emptyDataSetSource && [self.emptyDataSetSource respondsToSelector:@selector(imageTintColorForEmptyDataSet:)]) {
UIColor *color = [self.emptyDataSetSource imageTintColorForEmptyDataSet:self];
if (color) NSAssert([color isKindOfClass:[UIColor class]], @"You must return a valid UIColor object for -imageTintColorForEmptyDataSet:");
return color;
}
return nil;
}
- (NSAttributedString *)dzn_buttonTitleForState:(UIControlState)state
{
if (self.emptyDataSetSource && [self.emptyDataSetSource respondsToSelector:@selector(buttonTitleForEmptyDataSet:forState:)]) {
NSAttributedString *string = [self.emptyDataSetSource buttonTitleForEmptyDataSet:self forState:state];
if (string) NSAssert([string isKindOfClass:[NSAttributedString class]], @"You must return a valid NSAttributedString object for -buttonTitleForEmptyDataSet:forState:");
return string;
}
return nil;
}
- (UIImage *)dzn_buttonImageForState:(UIControlState)state
{
if (self.emptyDataSetSource && [self.emptyDataSetSource respondsToSelector:@selector(buttonImageForEmptyDataSet:forState:)]) {
UIImage *image = [self.emptyDataSetSource buttonImageForEmptyDataSet:self forState:state];
if (image) NSAssert([image isKindOfClass:[UIImage class]], @"You must return a valid UIImage object for -buttonImageForEmptyDataSet:forState:");
return image;
}
return nil;
}
- (UIImage *)dzn_buttonBackgroundImageForState:(UIControlState)state
{
if (self.emptyDataSetSource && [self.emptyDataSetSource respondsToSelector:@selector(buttonBackgroundImageForEmptyDataSet:forState:)]) {
UIImage *image = [self.emptyDataSetSource buttonBackgroundImageForEmptyDataSet:self forState:state];
if (image) NSAssert([image isKindOfClass:[UIImage class]], @"You must return a valid UIImage object for -buttonBackgroundImageForEmptyDataSet:forState:");
return image;
}
return nil;
}
- (UIColor *)dzn_dataSetBackgroundColor
{
if (self.emptyDataSetSource && [self.emptyDataSetSource respondsToSelector:@selector(backgroundColorForEmptyDataSet:)]) {
UIColor *color = [self.emptyDataSetSource backgroundColorForEmptyDataSet:self];
if (color) NSAssert([color isKindOfClass:[UIColor class]], @"You must return a valid UIColor object for -backgroundColorForEmptyDataSet:");
return color;
}
return [UIColor clearColor];
}
- (UIView *)dzn_customView
{
if (self.emptyDataSetSource && [self.emptyDataSetSource respondsToSelector:@selector(customViewForEmptyDataSet:)]) {
UIView *view = [self.emptyDataSetSource customViewForEmptyDataSet:self];
if (view) NSAssert([view isKindOfClass:[UIView class]], @"You must return a valid UIView object for -customViewForEmptyDataSet:");
return view;
}
return nil;
}
- (CGFloat)dzn_verticalOffset
{
CGFloat offset = 0.0;
if (self.emptyDataSetSource && [self.emptyDataSetSource respondsToSelector:@selector(verticalOffsetForEmptyDataSet:)]) {
offset = [self.emptyDataSetSource verticalOffsetForEmptyDataSet:self];
}
return offset;
}
- (CGFloat)dzn_verticalSpace
{
if (self.emptyDataSetSource && [self.emptyDataSetSource respondsToSelector:@selector(spaceHeightForEmptyDataSet:)]) {
return [self.emptyDataSetSource spaceHeightForEmptyDataSet:self];
}
return 0.0;
}
#pragma mark - Delegate Getters & Events (Private)
- (BOOL)dzn_shouldFadeIn {
if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetShouldFadeIn:)]) {
return [self.emptyDataSetDelegate emptyDataSetShouldFadeIn:self];
}
return YES;
}
- (BOOL)dzn_shouldDisplay
{
if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetShouldDisplay:)]) {
return [self.emptyDataSetDelegate emptyDataSetShouldDisplay:self];
}
return YES;
}
- (BOOL)dzn_shouldBeForcedToDisplay
{
if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetShouldBeForcedToDisplay:)]) {
return [self.emptyDataSetDelegate emptyDataSetShouldBeForcedToDisplay:self];
}
return NO;
}
- (BOOL)dzn_isTouchAllowed
{
if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetShouldAllowTouch:)]) {
return [self.emptyDataSetDelegate emptyDataSetShouldAllowTouch:self];
}
return YES;
}
- (BOOL)dzn_isScrollAllowed
{
if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetShouldAllowScroll:)]) {
return [self.emptyDataSetDelegate emptyDataSetShouldAllowScroll:self];
}
return NO;
}
- (BOOL)dzn_isImageViewAnimateAllowed
{
if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetShouldAnimateImageView:)]) {
return [self.emptyDataSetDelegate emptyDataSetShouldAnimateImageView:self];
}
return NO;
}
- (void)dzn_willAppear
{
if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetWillAppear:)]) {
[self.emptyDataSetDelegate emptyDataSetWillAppear:self];
}
}
- (void)dzn_didAppear
{
if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetDidAppear:)]) {
[self.emptyDataSetDelegate emptyDataSetDidAppear:self];
}
}
- (void)dzn_willDisappear
{
if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetWillDisappear:)]) {
[self.emptyDataSetDelegate emptyDataSetWillDisappear:self];
}
}
- (void)dzn_didDisappear
{
if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetDidDisappear:)]) {
[self.emptyDataSetDelegate emptyDataSetDidDisappear:self];
}
}
- (void)dzn_didTapContentView:(id)sender
{
if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSet:didTapView:)]) {
[self.emptyDataSetDelegate emptyDataSet:self didTapView:sender];
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
else if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetDidTapView:)]) {
[self.emptyDataSetDelegate emptyDataSetDidTapView:self];
}
#pragma clang diagnostic pop
}
- (void)dzn_didTapDataButton:(id)sender
{
if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSet:didTapButton:)]) {
[self.emptyDataSetDelegate emptyDataSet:self didTapButton:sender];
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
else if (self.emptyDataSetDelegate && [self.emptyDataSetDelegate respondsToSelector:@selector(emptyDataSetDidTapButton:)]) {
[self.emptyDataSetDelegate emptyDataSetDidTapButton:self];
}
#pragma clang diagnostic pop
}
#pragma mark - Setters (Public)
- (void)setEmptyDataSetSource:(id)datasource
{
if (!datasource || ![self dzn_canDisplay]) {
[self dzn_invalidate];
}
objc_setAssociatedObject(self, kEmptyDataSetSource, [[DZNWeakObjectContainer alloc] initWithWeakObject:datasource], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// We add method sizzling for injecting -dzn_reloadData implementation to the native -reloadData implementation
[self swizzleIfPossible:@selector(reloadData)];
// Exclusively for UITableView, we also inject -dzn_reloadData to -endUpdates
if ([self isKindOfClass:[UITableView class]]) {
[self swizzleIfPossible:@selector(endUpdates)];
}
}
- (void)setEmptyDataSetDelegate:(id)delegate
{
if (!delegate) {
[self dzn_invalidate];
}
objc_setAssociatedObject(self, kEmptyDataSetDelegate, [[DZNWeakObjectContainer alloc] initWithWeakObject:delegate], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#pragma mark - Setters (Private)
- (void)setEmptyDataSetView:(DZNEmptyDataSetView *)view
{
objc_setAssociatedObject(self, kEmptyDataSetView, view, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#pragma mark - Reload APIs (Public)
- (void)reloadEmptyDataSet
{
[self dzn_reloadEmptyDataSet];
}
#pragma mark - Reload APIs (Private)
- (void)dzn_reloadEmptyDataSet
{
if (![self dzn_canDisplay]) {
return;
}
if (([self dzn_shouldDisplay] && [self dzn_itemsCount] == 0) || [self dzn_shouldBeForcedToDisplay])
{
// Notifies that the empty dataset view will appear
[self dzn_willAppear];
DZNEmptyDataSetView *view = self.emptyDataSetView;
if (!view.superview) {
// Send the view all the way to the back, in case a header and/or footer is present, as well as for sectionHeaders or any other content
if (([self isKindOfClass:[UITableView class]] || [self isKindOfClass:[UICollectionView class]]) && self.subviews.count > 1) {
[self insertSubview:view atIndex:0];
}
else {
[self addSubview:view];
}
}
// Removing view resetting the view and its constraints it very important to guarantee a good state
[view prepareForReuse];
UIView *customView = [self dzn_customView];
// If a non-nil custom view is available, let's configure it instead
if (customView) {
view.customView = customView;
}
else {
// Get the data from the data source
NSAttributedString *titleLabelString = [self dzn_titleLabelString];
NSAttributedString *detailLabelString = [self dzn_detailLabelString];
UIImage *buttonImage = [self dzn_buttonImageForState:UIControlStateNormal];
NSAttributedString *buttonTitle = [self dzn_buttonTitleForState:UIControlStateNormal];
UIImage *image = [self dzn_image];
UIColor *imageTintColor = [self dzn_imageTintColor];
UIImageRenderingMode renderingMode = imageTintColor ? UIImageRenderingModeAlwaysTemplate : UIImageRenderingModeAlwaysOriginal;
view.verticalSpace = [self dzn_verticalSpace];
// Configure Image
if (image) {
if ([image respondsToSelector:@selector(imageWithRenderingMode:)]) {
view.imageView.image = [image imageWithRenderingMode:renderingMode];
view.imageView.tintColor = imageTintColor;
}
else {
// iOS 6 fallback: insert code to convert imaged if needed
view.imageView.image = image;
}
}
// Configure title label
if (titleLabelString) {
view.titleLabel.attributedText = titleLabelString;
}
// Configure detail label
if (detailLabelString) {
view.detailLabel.attributedText = detailLabelString;
}
// Configure button
if (buttonImage) {
[view.button setImage:buttonImage forState:UIControlStateNormal];
[view.button setImage:[self dzn_buttonImageForState:UIControlStateHighlighted] forState:UIControlStateHighlighted];
}
else if (buttonTitle) {
[view.button setAttributedTitle:buttonTitle forState:UIControlStateNormal];
[view.button setAttributedTitle:[self dzn_buttonTitleForState:UIControlStateHighlighted] forState:UIControlStateHighlighted];
[view.button setBackgroundImage:[self dzn_buttonBackgroundImageForState:UIControlStateNormal] forState:UIControlStateNormal];
[view.button setBackgroundImage:[self dzn_buttonBackgroundImageForState:UIControlStateHighlighted] forState:UIControlStateHighlighted];
}
}
// Configure offset
view.verticalOffset = [self dzn_verticalOffset];
// Configure the empty dataset view
view.backgroundColor = [self dzn_dataSetBackgroundColor];
view.hidden = NO;
view.clipsToBounds = YES;
// Configure empty dataset userInteraction permission
view.userInteractionEnabled = [self dzn_isTouchAllowed];
// Configure empty dataset fade in display
view.fadeInOnDisplay = [self dzn_shouldFadeIn];
[view setupConstraints];
[UIView performWithoutAnimation:^{
[view layoutIfNeeded];
}];
// Configure scroll permission
self.scrollEnabled = [self dzn_isScrollAllowed];
// Configure image view animation
if ([self dzn_isImageViewAnimateAllowed])
{
CAAnimation *animation = [self dzn_imageAnimation];
if (animation) {
[self.emptyDataSetView.imageView.layer addAnimation:animation forKey:kEmptyImageViewAnimationKey];
}
}
else if ([self.emptyDataSetView.imageView.layer animationForKey:kEmptyImageViewAnimationKey]) {
[self.emptyDataSetView.imageView.layer removeAnimationForKey:kEmptyImageViewAnimationKey];
}
// Notifies that the empty dataset view did appear
[self dzn_didAppear];
}
else if (self.isEmptyDataSetVisible) {
[self dzn_invalidate];
}
}
- (void)dzn_invalidate
{
// Notifies that the empty dataset view will disappear
[self dzn_willDisappear];
if (self.emptyDataSetView) {
[self.emptyDataSetView prepareForReuse];
[self.emptyDataSetView removeFromSuperview];
[self setEmptyDataSetView:nil];
}
self.scrollEnabled = YES;
// Notifies that the empty dataset view did disappear
[self dzn_didDisappear];
}
#pragma mark - Method Swizzling
static NSMutableDictionary *_impLookupTable;
static NSString *const DZNSwizzleInfoPointerKey = @"pointer";
static NSString *const DZNSwizzleInfoOwnerKey = @"owner";
static NSString *const DZNSwizzleInfoSelectorKey = @"selector";
// Based on Bryce Buchanan's swizzling technique http://blog.newrelic.com/2014/04/16/right-way-to-swizzle/
// And Juzzin's ideas https://github.com/juzzin/JUSEmptyViewController
void dzn_original_implementation(id self, SEL _cmd)
{
// Fetch original implementation from lookup table
Class baseClass = dzn_baseClassToSwizzleForTarget(self);
NSString *key = dzn_implementationKey(baseClass, _cmd);
NSDictionary *swizzleInfo = [_impLookupTable objectForKey:key];
NSValue *impValue = [swizzleInfo valueForKey:DZNSwizzleInfoPointerKey];
IMP impPointer = [impValue pointerValue];
// We then inject the additional implementation for reloading the empty dataset
// Doing it before calling the original implementation does update the 'isEmptyDataSetVisible' flag on time.
[self dzn_reloadEmptyDataSet];
// If found, call original implementation
if (impPointer) {
((void(*)(id,SEL))impPointer)(self,_cmd);
}
}
NSString *dzn_implementationKey(Class class, SEL selector)
{
if (!class || !selector) {
return nil;
}
NSString *className = NSStringFromClass([class class]);
NSString *selectorName = NSStringFromSelector(selector);
return [NSString stringWithFormat:@"%@_%@",className,selectorName];
}
Class dzn_baseClassToSwizzleForTarget(id target)
{
if ([target isKindOfClass:[UITableView class]]) {
return [UITableView class];
}
else if ([target isKindOfClass:[UICollectionView class]]) {
return [UICollectionView class];
}
else if ([target isKindOfClass:[UIScrollView class]]) {
return [UIScrollView class];
}
return nil;
}
- (void)swizzleIfPossible:(SEL)selector
{
// Check if the target responds to selector
if (![self respondsToSelector:selector]) {
return;
}
// Create the lookup table
if (!_impLookupTable) {
_impLookupTable = [[NSMutableDictionary alloc] initWithCapacity:3]; // 3 represent the supported base classes
}
// We make sure that setImplementation is called once per class kind, UITableView or UICollectionView.
for (NSDictionary *info in [_impLookupTable allValues]) {
Class class = [info objectForKey:DZNSwizzleInfoOwnerKey];
NSString *selectorName = [info objectForKey:DZNSwizzleInfoSelectorKey];
if ([selectorName isEqualToString:NSStringFromSelector(selector)]) {
if ([self isKindOfClass:class]) {
return;
}
}
}
Class baseClass = dzn_baseClassToSwizzleForTarget(self);
NSString *key = dzn_implementationKey(baseClass, selector);
NSValue *impValue = [[_impLookupTable objectForKey:key] valueForKey:DZNSwizzleInfoPointerKey];
// If the implementation for this class already exist, skip!!
if (impValue || !key || !baseClass) {
return;
}
// Swizzle by injecting additional implementation
Method method = class_getInstanceMethod(baseClass, selector);
IMP dzn_newImplementation = method_setImplementation(method, (IMP)dzn_original_implementation);
// Store the new implementation in the lookup table
NSDictionary *swizzledInfo = @{DZNSwizzleInfoOwnerKey: baseClass,
DZNSwizzleInfoSelectorKey: NSStringFromSelector(selector),
DZNSwizzleInfoPointerKey: [NSValue valueWithPointer:dzn_newImplementation]};
[_impLookupTable setObject:swizzledInfo forKey:key];
}
#pragma mark - UIGestureRecognizerDelegate Methods
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if ([gestureRecognizer.view isEqual:self.emptyDataSetView]) {
return [self dzn_isTouchAllowed];
}
return [super gestureRecognizerShouldBegin:gestureRecognizer];
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
UIGestureRecognizer *tapGesture = self.emptyDataSetView.tapGesture;
if ([gestureRecognizer isEqual:tapGesture] || [otherGestureRecognizer isEqual:tapGesture]) {
return YES;
}
// defer to emptyDataSetDelegate's implementation if available
if ( (self.emptyDataSetDelegate != (id)self) && [self.emptyDataSetDelegate respondsToSelector:@selector(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)]) {
return [(id)self.emptyDataSetDelegate gestureRecognizer:gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:otherGestureRecognizer];
}
return NO;
}
@end
#pragma mark - DZNEmptyDataSetView
@interface DZNEmptyDataSetView ()
@end
@implementation DZNEmptyDataSetView
@synthesize contentView = _contentView;
@synthesize titleLabel = _titleLabel, detailLabel = _detailLabel, imageView = _imageView, button = _button;
#pragma mark - Initialization Methods
- (instancetype)init
{
self = [super init];
if (self) {
[self addSubview:self.contentView];
}
return self;
}
- (void)didMoveToSuperview
{
self.frame = self.superview.bounds;
void(^fadeInBlock)(void) = ^{_contentView.alpha = 1.0;};
if (self.fadeInOnDisplay) {
[UIView animateWithDuration:0.25
animations:fadeInBlock
completion:NULL];
}
else {
fadeInBlock();
}
}
#pragma mark - Getters
- (UIView *)contentView
{
if (!_contentView)
{
_contentView = [UIView new];
_contentView.translatesAutoresizingMaskIntoConstraints = NO;
_contentView.backgroundColor = [UIColor clearColor];
_contentView.userInteractionEnabled = YES;
_contentView.alpha = 0;
}
return _contentView;
}
- (UIImageView *)imageView
{
if (!_imageView)
{
_imageView = [UIImageView new];
_imageView.translatesAutoresizingMaskIntoConstraints = NO;
_imageView.backgroundColor = [UIColor clearColor];
_imageView.contentMode = UIViewContentModeScaleAspectFit;
_imageView.userInteractionEnabled = NO;
_imageView.accessibilityIdentifier = @"empty set background image";
[_contentView addSubview:_imageView];
}
return _imageView;
}
- (UILabel *)titleLabel
{
if (!_titleLabel)
{
_titleLabel = [UILabel new];
_titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
_titleLabel.backgroundColor = [UIColor clearColor];
_titleLabel.font = [UIFont systemFontOfSize:27.0];
_titleLabel.textColor = [UIColor colorWithWhite:0.6 alpha:1.0];
_titleLabel.textAlignment = NSTextAlignmentCenter;
_titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
_titleLabel.numberOfLines = 0;
_titleLabel.accessibilityIdentifier = @"empty set title";
[_contentView addSubview:_titleLabel];
}
return _titleLabel;
}
- (UILabel *)detailLabel
{
if (!_detailLabel)
{
_detailLabel = [UILabel new];
_detailLabel.translatesAutoresizingMaskIntoConstraints = NO;
_detailLabel.backgroundColor = [UIColor clearColor];
_detailLabel.font = [UIFont systemFontOfSize:17.0];
_detailLabel.textColor = [UIColor colorWithWhite:0.6 alpha:1.0];
_detailLabel.textAlignment = NSTextAlignmentCenter;
_detailLabel.lineBreakMode = NSLineBreakByWordWrapping;
_detailLabel.numberOfLines = 0;
_detailLabel.accessibilityIdentifier = @"empty set detail label";
[_contentView addSubview:_detailLabel];
}
return _detailLabel;
}
- (UIButton *)button
{
if (!_button)
{
_button = [UIButton buttonWithType:UIButtonTypeCustom];
_button.translatesAutoresizingMaskIntoConstraints = NO;
_button.backgroundColor = [UIColor clearColor];
_button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
_button.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
_button.accessibilityIdentifier = @"empty set button";
[_button addTarget:self action:@selector(didTapButton:) forControlEvents:UIControlEventTouchUpInside];
[_contentView addSubview:_button];
}
return _button;
}
- (BOOL)canShowImage
{
return (_imageView.image && _imageView.superview);
}
- (BOOL)canShowTitle
{
return (_titleLabel.attributedText.string.length > 0 && _titleLabel.superview);
}
- (BOOL)canShowDetail
{
return (_detailLabel.attributedText.string.length > 0 && _detailLabel.superview);
}
- (BOOL)canShowButton
{
if ([_button attributedTitleForState:UIControlStateNormal].string.length > 0 || [_button imageForState:UIControlStateNormal]) {
return (_button.superview != nil);
}
return NO;
}
#pragma mark - Setters
- (void)setCustomView:(UIView *)view
{
if (!view) {
return;
}
if (_customView) {
[_customView removeFromSuperview];
_customView = nil;
}
_customView = view;
_customView.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView addSubview:_customView];
}
#pragma mark - Action Methods
- (void)didTapButton:(id)sender
{
SEL selector = NSSelectorFromString(@"dzn_didTapDataButton:");
if ([self.superview respondsToSelector:selector]) {
[self.superview performSelector:selector withObject:sender afterDelay:0.0f];
}
}
- (void)removeAllConstraints
{
[self removeConstraints:self.constraints];
[_contentView removeConstraints:_contentView.constraints];
}
- (void)prepareForReuse
{
[self.contentView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
_titleLabel = nil;
_detailLabel = nil;
_imageView = nil;
_button = nil;
_customView = nil;
[self removeAllConstraints];
}
#pragma mark - Auto-Layout Configuration
- (void)setupConstraints
{
// First, configure the content view constaints
// The content view must alway be centered to its superview
NSLayoutConstraint *centerXConstraint = [self equallyRelatedConstraintWithView:self.contentView attribute:NSLayoutAttributeCenterX];
NSLayoutConstraint *centerYConstraint = [self equallyRelatedConstraintWithView:self.contentView attribute:NSLayoutAttributeCenterY];
[self addConstraint:centerXConstraint];
[self addConstraint:centerYConstraint];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[contentView]|" options:0 metrics:nil views:@{@"contentView": self.contentView}]];
// When a custom offset is available, we adjust the vertical constraints' constants
if (self.verticalOffset != 0 && self.constraints.count > 0) {
centerYConstraint.constant = self.verticalOffset;
}
// If applicable, set the custom view's constraints
if (_customView) {
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[customView]|" options:0 metrics:nil views:@{@"customView":_customView}]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[customView]|" options:0 metrics:nil views:@{@"customView":_customView}]];
}
else {
CGFloat width = CGRectGetWidth(self.frame) ? : CGRectGetWidth([UIScreen mainScreen].bounds);
CGFloat padding = roundf(width/16.0);
CGFloat verticalSpace = self.verticalSpace ? : 11.0; // Default is 11 pts
NSMutableArray *subviewStrings = [NSMutableArray array];
NSMutableDictionary *views = [NSMutableDictionary dictionary];
NSDictionary *metrics = @{@"padding": @(padding)};
// Assign the image view's horizontal constraints
if (_imageView.superview) {
[subviewStrings addObject:@"imageView"];
views[[subviewStrings lastObject]] = _imageView;
[self.contentView addConstraint:[self.contentView equallyRelatedConstraintWithView:_imageView attribute:NSLayoutAttributeCenterX]];
}
// Assign the title label's horizontal constraints
if ([self canShowTitle]) {
[subviewStrings addObject:@"titleLabel"];
views[[subviewStrings lastObject]] = _titleLabel;
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(padding@750)-[titleLabel(>=0)]-(padding@750)-|"
options:0 metrics:metrics views:views]];
}
// or removes from its superview
else {
[_titleLabel removeFromSuperview];
_titleLabel = nil;
}
// Assign the detail label's horizontal constraints
if ([self canShowDetail]) {
[subviewStrings addObject:@"detailLabel"];
views[[subviewStrings lastObject]] = _detailLabel;
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(padding@750)-[detailLabel(>=0)]-(padding@750)-|"
options:0 metrics:metrics views:views]];
}
// or removes from its superview
else {
[_detailLabel removeFromSuperview];
_detailLabel = nil;
}
// Assign the button's horizontal constraints
if ([self canShowButton]) {
[subviewStrings addObject:@"button"];
views[[subviewStrings lastObject]] = _button;
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(padding@750)-[button(>=0)]-(padding@750)-|"
options:0 metrics:metrics views:views]];
}
// or removes from its superview
else {
[_button removeFromSuperview];
_button = nil;
}
NSMutableString *verticalFormat = [NSMutableString new];
// Build a dynamic string format for the vertical constraints, adding a margin between each element. Default is 11 pts.
for (int i = 0; i < subviewStrings.count; i++) {
NSString *string = subviewStrings[i];
[verticalFormat appendFormat:@"[%@]", string];
if (i < subviewStrings.count-1) {
[verticalFormat appendFormat:@"-(%.f@750)-", verticalSpace];
}
}
// Assign the vertical constraints to the content view
if (verticalFormat.length > 0) {
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:@"V:|%@|", verticalFormat]
options:0 metrics:metrics views:views]];
}
}
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *hitView = [super hitTest:point withEvent:event];
// Return any UIControl instance such as buttons, segmented controls, switches, etc.
if ([hitView isKindOfClass:[UIControl class]]) {
return hitView;
}
// Return either the contentView or customView
if ([hitView isEqual:_contentView] || [hitView isEqual:_customView]) {
return hitView;
}
return nil;
}
@end
#pragma mark - UIView+DZNConstraintBasedLayoutExtensions
@implementation UIView (DZNConstraintBasedLayoutExtensions)
- (NSLayoutConstraint *)equallyRelatedConstraintWithView:(UIView *)view attribute:(NSLayoutAttribute)attribute
{
return [NSLayoutConstraint constraintWithItem:view
attribute:attribute
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:attribute
multiplier:1.0
constant:0.0];
}
@end
#pragma mark - DZNWeakObjectContainer
@implementation DZNWeakObjectContainer
- (instancetype)initWithWeakObject:(id)object
{
self = [super init];
if (self) {
_weakObject = object;
}
return self;
}
@end
================================================
FILE: Pods/Masonry/LICENSE
================================================
Copyright (c) 2011-2012 Masonry Team - https://github.com/Masonry
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.
================================================
FILE: Pods/Masonry/Masonry/MASCompositeConstraint.h
================================================
//
// MASCompositeConstraint.h
// Masonry
//
// Created by Jonas Budelmann on 21/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASConstraint.h"
#import "MASUtilities.h"
/**
* A group of MASConstraint objects
*/
@interface MASCompositeConstraint : MASConstraint
/**
* Creates a composite with a predefined array of children
*
* @param children child MASConstraints
*
* @return a composite constraint
*/
- (id)initWithChildren:(NSArray *)children;
@end
================================================
FILE: Pods/Masonry/Masonry/MASCompositeConstraint.m
================================================
//
// MASCompositeConstraint.m
// Masonry
//
// Created by Jonas Budelmann on 21/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASCompositeConstraint.h"
#import "MASConstraint+Private.h"
@interface MASCompositeConstraint ()
@property (nonatomic, strong) id mas_key;
@property (nonatomic, strong) NSMutableArray *childConstraints;
@end
@implementation MASCompositeConstraint
- (id)initWithChildren:(NSArray *)children {
self = [super init];
if (!self) return nil;
_childConstraints = [children mutableCopy];
for (MASConstraint *constraint in _childConstraints) {
constraint.delegate = self;
}
return self;
}
#pragma mark - MASConstraintDelegate
- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint {
NSUInteger index = [self.childConstraints indexOfObject:constraint];
NSAssert(index != NSNotFound, @"Could not find constraint %@", constraint);
[self.childConstraints replaceObjectAtIndex:index withObject:replacementConstraint];
}
- (MASConstraint *)constraint:(MASConstraint __unused *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
id strongDelegate = self.delegate;
MASConstraint *newConstraint = [strongDelegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
newConstraint.delegate = self;
[self.childConstraints addObject:newConstraint];
return newConstraint;
}
#pragma mark - NSLayoutConstraint multiplier proxies
- (MASConstraint * (^)(CGFloat))multipliedBy {
return ^id(CGFloat multiplier) {
for (MASConstraint *constraint in self.childConstraints) {
constraint.multipliedBy(multiplier);
}
return self;
};
}
- (MASConstraint * (^)(CGFloat))dividedBy {
return ^id(CGFloat divider) {
for (MASConstraint *constraint in self.childConstraints) {
constraint.dividedBy(divider);
}
return self;
};
}
#pragma mark - MASLayoutPriority proxy
- (MASConstraint * (^)(MASLayoutPriority))priority {
return ^id(MASLayoutPriority priority) {
for (MASConstraint *constraint in self.childConstraints) {
constraint.priority(priority);
}
return self;
};
}
#pragma mark - NSLayoutRelation proxy
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
return ^id(id attr, NSLayoutRelation relation) {
for (MASConstraint *constraint in self.childConstraints.copy) {
constraint.equalToWithRelation(attr, relation);
}
return self;
};
}
#pragma mark - attribute chaining
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
[self constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
return self;
}
#pragma mark - Animator proxy
#if TARGET_OS_MAC && !(TARGET_OS_IPHONE || TARGET_OS_TV)
- (MASConstraint *)animator {
for (MASConstraint *constraint in self.childConstraints) {
[constraint animator];
}
return self;
}
#endif
#pragma mark - debug helpers
- (MASConstraint * (^)(id))key {
return ^id(id key) {
self.mas_key = key;
int i = 0;
for (MASConstraint *constraint in self.childConstraints) {
constraint.key([NSString stringWithFormat:@"%@[%d]", key, i++]);
}
return self;
};
}
#pragma mark - NSLayoutConstraint constant setters
- (void)setInsets:(MASEdgeInsets)insets {
for (MASConstraint *constraint in self.childConstraints) {
constraint.insets = insets;
}
}
- (void)setInset:(CGFloat)inset {
for (MASConstraint *constraint in self.childConstraints) {
constraint.inset = inset;
}
}
- (void)setOffset:(CGFloat)offset {
for (MASConstraint *constraint in self.childConstraints) {
constraint.offset = offset;
}
}
- (void)setSizeOffset:(CGSize)sizeOffset {
for (MASConstraint *constraint in self.childConstraints) {
constraint.sizeOffset = sizeOffset;
}
}
- (void)setCenterOffset:(CGPoint)centerOffset {
for (MASConstraint *constraint in self.childConstraints) {
constraint.centerOffset = centerOffset;
}
}
#pragma mark - MASConstraint
- (void)activate {
for (MASConstraint *constraint in self.childConstraints) {
[constraint activate];
}
}
- (void)deactivate {
for (MASConstraint *constraint in self.childConstraints) {
[constraint deactivate];
}
}
- (void)install {
for (MASConstraint *constraint in self.childConstraints) {
constraint.updateExisting = self.updateExisting;
[constraint install];
}
}
- (void)uninstall {
for (MASConstraint *constraint in self.childConstraints) {
[constraint uninstall];
}
}
@end
================================================
FILE: Pods/Masonry/Masonry/MASConstraint+Private.h
================================================
//
// MASConstraint+Private.h
// Masonry
//
// Created by Nick Tymchenko on 29/04/14.
// Copyright (c) 2014 cloudling. All rights reserved.
//
#import "MASConstraint.h"
@protocol MASConstraintDelegate;
@interface MASConstraint ()
/**
* Whether or not to check for an existing constraint instead of adding constraint
*/
@property (nonatomic, assign) BOOL updateExisting;
/**
* Usually MASConstraintMaker but could be a parent MASConstraint
*/
@property (nonatomic, weak) id delegate;
/**
* Based on a provided value type, is equal to calling:
* NSNumber - setOffset:
* NSValue with CGPoint - setPointOffset:
* NSValue with CGSize - setSizeOffset:
* NSValue with MASEdgeInsets - setInsets:
*/
- (void)setLayoutConstantWithValue:(NSValue *)value;
@end
@interface MASConstraint (Abstract)
/**
* Sets the constraint relation to given NSLayoutRelation
* returns a block which accepts one of the following:
* MASViewAttribute, UIView, NSValue, NSArray
* see readme for more details.
*/
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation;
/**
* Override to set a custom chaining behaviour
*/
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute;
@end
@protocol MASConstraintDelegate
/**
* Notifies the delegate when the constraint needs to be replaced with another constraint. For example
* A MASViewConstraint may turn into a MASCompositeConstraint when an array is passed to one of the equality blocks
*/
- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint;
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute;
@end
================================================
FILE: Pods/Masonry/Masonry/MASConstraint.h
================================================
//
// MASConstraint.h
// Masonry
//
// Created by Jonas Budelmann on 22/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASUtilities.h"
/**
* Enables Constraints to be created with chainable syntax
* Constraint can represent single NSLayoutConstraint (MASViewConstraint)
* or a group of NSLayoutConstraints (MASComposisteConstraint)
*/
@interface MASConstraint : NSObject
// Chaining Support
/**
* Modifies the NSLayoutConstraint constant,
* only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following
* NSLayoutAttributeTop, NSLayoutAttributeLeft, NSLayoutAttributeBottom, NSLayoutAttributeRight
*/
- (MASConstraint * (^)(MASEdgeInsets insets))insets;
/**
* Modifies the NSLayoutConstraint constant,
* only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following
* NSLayoutAttributeTop, NSLayoutAttributeLeft, NSLayoutAttributeBottom, NSLayoutAttributeRight
*/
- (MASConstraint * (^)(CGFloat inset))inset;
/**
* Modifies the NSLayoutConstraint constant,
* only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following
* NSLayoutAttributeWidth, NSLayoutAttributeHeight
*/
- (MASConstraint * (^)(CGSize offset))sizeOffset;
/**
* Modifies the NSLayoutConstraint constant,
* only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following
* NSLayoutAttributeCenterX, NSLayoutAttributeCenterY
*/
- (MASConstraint * (^)(CGPoint offset))centerOffset;
/**
* Modifies the NSLayoutConstraint constant
*/
- (MASConstraint * (^)(CGFloat offset))offset;
/**
* Modifies the NSLayoutConstraint constant based on a value type
*/
- (MASConstraint * (^)(NSValue *value))valueOffset;
/**
* Sets the NSLayoutConstraint multiplier property
*/
- (MASConstraint * (^)(CGFloat multiplier))multipliedBy;
/**
* Sets the NSLayoutConstraint multiplier to 1.0/dividedBy
*/
- (MASConstraint * (^)(CGFloat divider))dividedBy;
/**
* Sets the NSLayoutConstraint priority to a float or MASLayoutPriority
*/
- (MASConstraint * (^)(MASLayoutPriority priority))priority;
/**
* Sets the NSLayoutConstraint priority to MASLayoutPriorityLow
*/
- (MASConstraint * (^)(void))priorityLow;
/**
* Sets the NSLayoutConstraint priority to MASLayoutPriorityMedium
*/
- (MASConstraint * (^)(void))priorityMedium;
/**
* Sets the NSLayoutConstraint priority to MASLayoutPriorityHigh
*/
- (MASConstraint * (^)(void))priorityHigh;
/**
* Sets the constraint relation to NSLayoutRelationEqual
* returns a block which accepts one of the following:
* MASViewAttribute, UIView, NSValue, NSArray
* see readme for more details.
*/
- (MASConstraint * (^)(id attr))equalTo;
/**
* Sets the constraint relation to NSLayoutRelationGreaterThanOrEqual
* returns a block which accepts one of the following:
* MASViewAttribute, UIView, NSValue, NSArray
* see readme for more details.
*/
- (MASConstraint * (^)(id attr))greaterThanOrEqualTo;
/**
* Sets the constraint relation to NSLayoutRelationLessThanOrEqual
* returns a block which accepts one of the following:
* MASViewAttribute, UIView, NSValue, NSArray
* see readme for more details.
*/
- (MASConstraint * (^)(id attr))lessThanOrEqualTo;
/**
* Optional semantic property which has no effect but improves the readability of constraint
*/
- (MASConstraint *)with;
/**
* Optional semantic property which has no effect but improves the readability of constraint
*/
- (MASConstraint *)and;
/**
* Creates a new MASCompositeConstraint with the called attribute and reciever
*/
- (MASConstraint *)left;
- (MASConstraint *)top;
- (MASConstraint *)right;
- (MASConstraint *)bottom;
- (MASConstraint *)leading;
- (MASConstraint *)trailing;
- (MASConstraint *)width;
- (MASConstraint *)height;
- (MASConstraint *)centerX;
- (MASConstraint *)centerY;
- (MASConstraint *)baseline;
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
- (MASConstraint *)firstBaseline;
- (MASConstraint *)lastBaseline;
#endif
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)
- (MASConstraint *)leftMargin;
- (MASConstraint *)rightMargin;
- (MASConstraint *)topMargin;
- (MASConstraint *)bottomMargin;
- (MASConstraint *)leadingMargin;
- (MASConstraint *)trailingMargin;
- (MASConstraint *)centerXWithinMargins;
- (MASConstraint *)centerYWithinMargins;
#endif
/**
* Sets the constraint debug name
*/
- (MASConstraint * (^)(id key))key;
// NSLayoutConstraint constant Setters
// for use outside of mas_updateConstraints/mas_makeConstraints blocks
/**
* Modifies the NSLayoutConstraint constant,
* only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following
* NSLayoutAttributeTop, NSLayoutAttributeLeft, NSLayoutAttributeBottom, NSLayoutAttributeRight
*/
- (void)setInsets:(MASEdgeInsets)insets;
/**
* Modifies the NSLayoutConstraint constant,
* only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following
* NSLayoutAttributeTop, NSLayoutAttributeLeft, NSLayoutAttributeBottom, NSLayoutAttributeRight
*/
- (void)setInset:(CGFloat)inset;
/**
* Modifies the NSLayoutConstraint constant,
* only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following
* NSLayoutAttributeWidth, NSLayoutAttributeHeight
*/
- (void)setSizeOffset:(CGSize)sizeOffset;
/**
* Modifies the NSLayoutConstraint constant,
* only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following
* NSLayoutAttributeCenterX, NSLayoutAttributeCenterY
*/
- (void)setCenterOffset:(CGPoint)centerOffset;
/**
* Modifies the NSLayoutConstraint constant
*/
- (void)setOffset:(CGFloat)offset;
// NSLayoutConstraint Installation support
#if TARGET_OS_MAC && !(TARGET_OS_IPHONE || TARGET_OS_TV)
/**
* Whether or not to go through the animator proxy when modifying the constraint
*/
@property (nonatomic, copy, readonly) MASConstraint *animator;
#endif
/**
* Activates an NSLayoutConstraint if it's supported by an OS.
* Invokes install otherwise.
*/
- (void)activate;
/**
* Deactivates previously installed/activated NSLayoutConstraint.
*/
- (void)deactivate;
/**
* Creates a NSLayoutConstraint and adds it to the appropriate view.
*/
- (void)install;
/**
* Removes previously installed NSLayoutConstraint
*/
- (void)uninstall;
@end
/**
* Convenience auto-boxing macros for MASConstraint methods.
*
* Defining MAS_SHORTHAND_GLOBALS will turn on auto-boxing for default syntax.
* A potential drawback of this is that the unprefixed macros will appear in global scope.
*/
#define mas_equalTo(...) equalTo(MASBoxValue((__VA_ARGS__)))
#define mas_greaterThanOrEqualTo(...) greaterThanOrEqualTo(MASBoxValue((__VA_ARGS__)))
#define mas_lessThanOrEqualTo(...) lessThanOrEqualTo(MASBoxValue((__VA_ARGS__)))
#define mas_offset(...) valueOffset(MASBoxValue((__VA_ARGS__)))
#ifdef MAS_SHORTHAND_GLOBALS
#define equalTo(...) mas_equalTo(__VA_ARGS__)
#define greaterThanOrEqualTo(...) mas_greaterThanOrEqualTo(__VA_ARGS__)
#define lessThanOrEqualTo(...) mas_lessThanOrEqualTo(__VA_ARGS__)
#define offset(...) mas_offset(__VA_ARGS__)
#endif
@interface MASConstraint (AutoboxingSupport)
/**
* Aliases to corresponding relation methods (for shorthand macros)
* Also needed to aid autocompletion
*/
- (MASConstraint * (^)(id attr))mas_equalTo;
- (MASConstraint * (^)(id attr))mas_greaterThanOrEqualTo;
- (MASConstraint * (^)(id attr))mas_lessThanOrEqualTo;
/**
* A dummy method to aid autocompletion
*/
- (MASConstraint * (^)(id offset))mas_offset;
@end
================================================
FILE: Pods/Masonry/Masonry/MASConstraint.m
================================================
//
// MASConstraint.m
// Masonry
//
// Created by Nick Tymchenko on 1/20/14.
//
#import "MASConstraint.h"
#import "MASConstraint+Private.h"
#define MASMethodNotImplemented() \
@throw [NSException exceptionWithName:NSInternalInconsistencyException \
reason:[NSString stringWithFormat:@"You must override %@ in a subclass.", NSStringFromSelector(_cmd)] \
userInfo:nil]
@implementation MASConstraint
#pragma mark - Init
- (id)init {
NSAssert(![self isMemberOfClass:[MASConstraint class]], @"MASConstraint is an abstract class, you should not instantiate it directly.");
return [super init];
}
#pragma mark - NSLayoutRelation proxies
- (MASConstraint * (^)(id))equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
- (MASConstraint * (^)(id))mas_equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
- (MASConstraint * (^)(id))greaterThanOrEqualTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationGreaterThanOrEqual);
};
}
- (MASConstraint * (^)(id))mas_greaterThanOrEqualTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationGreaterThanOrEqual);
};
}
- (MASConstraint * (^)(id))lessThanOrEqualTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationLessThanOrEqual);
};
}
- (MASConstraint * (^)(id))mas_lessThanOrEqualTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationLessThanOrEqual);
};
}
#pragma mark - MASLayoutPriority proxies
- (MASConstraint * (^)(void))priorityLow {
return ^id{
self.priority(MASLayoutPriorityDefaultLow);
return self;
};
}
- (MASConstraint * (^)(void))priorityMedium {
return ^id{
self.priority(MASLayoutPriorityDefaultMedium);
return self;
};
}
- (MASConstraint * (^)(void))priorityHigh {
return ^id{
self.priority(MASLayoutPriorityDefaultHigh);
return self;
};
}
#pragma mark - NSLayoutConstraint constant proxies
- (MASConstraint * (^)(MASEdgeInsets))insets {
return ^id(MASEdgeInsets insets){
self.insets = insets;
return self;
};
}
- (MASConstraint * (^)(CGFloat))inset {
return ^id(CGFloat inset){
self.inset = inset;
return self;
};
}
- (MASConstraint * (^)(CGSize))sizeOffset {
return ^id(CGSize offset) {
self.sizeOffset = offset;
return self;
};
}
- (MASConstraint * (^)(CGPoint))centerOffset {
return ^id(CGPoint offset) {
self.centerOffset = offset;
return self;
};
}
- (MASConstraint * (^)(CGFloat))offset {
return ^id(CGFloat offset){
self.offset = offset;
return self;
};
}
- (MASConstraint * (^)(NSValue *value))valueOffset {
return ^id(NSValue *offset) {
NSAssert([offset isKindOfClass:NSValue.class], @"expected an NSValue offset, got: %@", offset);
[self setLayoutConstantWithValue:offset];
return self;
};
}
- (MASConstraint * (^)(id offset))mas_offset {
// Will never be called due to macro
return nil;
}
#pragma mark - NSLayoutConstraint constant setter
- (void)setLayoutConstantWithValue:(NSValue *)value {
if ([value isKindOfClass:NSNumber.class]) {
self.offset = [(NSNumber *)value doubleValue];
} else if (strcmp(value.objCType, @encode(CGPoint)) == 0) {
CGPoint point;
[value getValue:&point];
self.centerOffset = point;
} else if (strcmp(value.objCType, @encode(CGSize)) == 0) {
CGSize size;
[value getValue:&size];
self.sizeOffset = size;
} else if (strcmp(value.objCType, @encode(MASEdgeInsets)) == 0) {
MASEdgeInsets insets;
[value getValue:&insets];
self.insets = insets;
} else {
NSAssert(NO, @"attempting to set layout constant with unsupported value: %@", value);
}
}
#pragma mark - Semantic properties
- (MASConstraint *)with {
return self;
}
- (MASConstraint *)and {
return self;
}
#pragma mark - Chaining
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute __unused)layoutAttribute {
MASMethodNotImplemented();
}
- (MASConstraint *)left {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
- (MASConstraint *)top {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
- (MASConstraint *)right {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight];
}
- (MASConstraint *)bottom {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBottom];
}
- (MASConstraint *)leading {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeading];
}
- (MASConstraint *)trailing {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTrailing];
}
- (MASConstraint *)width {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeWidth];
}
- (MASConstraint *)height {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeHeight];
}
- (MASConstraint *)centerX {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterX];
}
- (MASConstraint *)centerY {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterY];
}
- (MASConstraint *)baseline {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBaseline];
}
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
- (MASConstraint *)firstBaseline {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeFirstBaseline];
}
- (MASConstraint *)lastBaseline {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLastBaseline];
}
#endif
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)
- (MASConstraint *)leftMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeftMargin];
}
- (MASConstraint *)rightMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRightMargin];
}
- (MASConstraint *)topMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTopMargin];
}
- (MASConstraint *)bottomMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBottomMargin];
}
- (MASConstraint *)leadingMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeadingMargin];
}
- (MASConstraint *)trailingMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTrailingMargin];
}
- (MASConstraint *)centerXWithinMargins {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterXWithinMargins];
}
- (MASConstraint *)centerYWithinMargins {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterYWithinMargins];
}
#endif
#pragma mark - Abstract
- (MASConstraint * (^)(CGFloat multiplier))multipliedBy { MASMethodNotImplemented(); }
- (MASConstraint * (^)(CGFloat divider))dividedBy { MASMethodNotImplemented(); }
- (MASConstraint * (^)(MASLayoutPriority priority))priority { MASMethodNotImplemented(); }
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation { MASMethodNotImplemented(); }
- (MASConstraint * (^)(id key))key { MASMethodNotImplemented(); }
- (void)setInsets:(MASEdgeInsets __unused)insets { MASMethodNotImplemented(); }
- (void)setInset:(CGFloat __unused)inset { MASMethodNotImplemented(); }
- (void)setSizeOffset:(CGSize __unused)sizeOffset { MASMethodNotImplemented(); }
- (void)setCenterOffset:(CGPoint __unused)centerOffset { MASMethodNotImplemented(); }
- (void)setOffset:(CGFloat __unused)offset { MASMethodNotImplemented(); }
#if TARGET_OS_MAC && !(TARGET_OS_IPHONE || TARGET_OS_TV)
- (MASConstraint *)animator { MASMethodNotImplemented(); }
#endif
- (void)activate { MASMethodNotImplemented(); }
- (void)deactivate { MASMethodNotImplemented(); }
- (void)install { MASMethodNotImplemented(); }
- (void)uninstall { MASMethodNotImplemented(); }
@end
================================================
FILE: Pods/Masonry/Masonry/MASConstraintMaker.h
================================================
//
// MASConstraintMaker.h
// Masonry
//
// Created by Jonas Budelmann on 20/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASConstraint.h"
#import "MASUtilities.h"
typedef NS_OPTIONS(NSInteger, MASAttribute) {
MASAttributeLeft = 1 << NSLayoutAttributeLeft,
MASAttributeRight = 1 << NSLayoutAttributeRight,
MASAttributeTop = 1 << NSLayoutAttributeTop,
MASAttributeBottom = 1 << NSLayoutAttributeBottom,
MASAttributeLeading = 1 << NSLayoutAttributeLeading,
MASAttributeTrailing = 1 << NSLayoutAttributeTrailing,
MASAttributeWidth = 1 << NSLayoutAttributeWidth,
MASAttributeHeight = 1 << NSLayoutAttributeHeight,
MASAttributeCenterX = 1 << NSLayoutAttributeCenterX,
MASAttributeCenterY = 1 << NSLayoutAttributeCenterY,
MASAttributeBaseline = 1 << NSLayoutAttributeBaseline,
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
MASAttributeFirstBaseline = 1 << NSLayoutAttributeFirstBaseline,
MASAttributeLastBaseline = 1 << NSLayoutAttributeLastBaseline,
#endif
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)
MASAttributeLeftMargin = 1 << NSLayoutAttributeLeftMargin,
MASAttributeRightMargin = 1 << NSLayoutAttributeRightMargin,
MASAttributeTopMargin = 1 << NSLayoutAttributeTopMargin,
MASAttributeBottomMargin = 1 << NSLayoutAttributeBottomMargin,
MASAttributeLeadingMargin = 1 << NSLayoutAttributeLeadingMargin,
MASAttributeTrailingMargin = 1 << NSLayoutAttributeTrailingMargin,
MASAttributeCenterXWithinMargins = 1 << NSLayoutAttributeCenterXWithinMargins,
MASAttributeCenterYWithinMargins = 1 << NSLayoutAttributeCenterYWithinMargins,
#endif
};
/**
* Provides factory methods for creating MASConstraints.
* Constraints are collected until they are ready to be installed
*
*/
@interface MASConstraintMaker : NSObject
/**
* The following properties return a new MASViewConstraint
* with the first item set to the makers associated view and the appropriate MASViewAttribute
*/
@property (nonatomic, strong, readonly) MASConstraint *left;
@property (nonatomic, strong, readonly) MASConstraint *top;
@property (nonatomic, strong, readonly) MASConstraint *right;
@property (nonatomic, strong, readonly) MASConstraint *bottom;
@property (nonatomic, strong, readonly) MASConstraint *leading;
@property (nonatomic, strong, readonly) MASConstraint *trailing;
@property (nonatomic, strong, readonly) MASConstraint *width;
@property (nonatomic, strong, readonly) MASConstraint *height;
@property (nonatomic, strong, readonly) MASConstraint *centerX;
@property (nonatomic, strong, readonly) MASConstraint *centerY;
@property (nonatomic, strong, readonly) MASConstraint *baseline;
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
@property (nonatomic, strong, readonly) MASConstraint *firstBaseline;
@property (nonatomic, strong, readonly) MASConstraint *lastBaseline;
#endif
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)
@property (nonatomic, strong, readonly) MASConstraint *leftMargin;
@property (nonatomic, strong, readonly) MASConstraint *rightMargin;
@property (nonatomic, strong, readonly) MASConstraint *topMargin;
@property (nonatomic, strong, readonly) MASConstraint *bottomMargin;
@property (nonatomic, strong, readonly) MASConstraint *leadingMargin;
@property (nonatomic, strong, readonly) MASConstraint *trailingMargin;
@property (nonatomic, strong, readonly) MASConstraint *centerXWithinMargins;
@property (nonatomic, strong, readonly) MASConstraint *centerYWithinMargins;
#endif
/**
* Returns a block which creates a new MASCompositeConstraint with the first item set
* to the makers associated view and children corresponding to the set bits in the
* MASAttribute parameter. Combine multiple attributes via binary-or.
*/
@property (nonatomic, strong, readonly) MASConstraint *(^attributes)(MASAttribute attrs);
/**
* Creates a MASCompositeConstraint with type MASCompositeConstraintTypeEdges
* which generates the appropriate MASViewConstraint children (top, left, bottom, right)
* with the first item set to the makers associated view
*/
@property (nonatomic, strong, readonly) MASConstraint *edges;
/**
* Creates a MASCompositeConstraint with type MASCompositeConstraintTypeSize
* which generates the appropriate MASViewConstraint children (width, height)
* with the first item set to the makers associated view
*/
@property (nonatomic, strong, readonly) MASConstraint *size;
/**
* Creates a MASCompositeConstraint with type MASCompositeConstraintTypeCenter
* which generates the appropriate MASViewConstraint children (centerX, centerY)
* with the first item set to the makers associated view
*/
@property (nonatomic, strong, readonly) MASConstraint *center;
/**
* Whether or not to check for an existing constraint instead of adding constraint
*/
@property (nonatomic, assign) BOOL updateExisting;
/**
* Whether or not to remove existing constraints prior to installing
*/
@property (nonatomic, assign) BOOL removeExisting;
/**
* initialises the maker with a default view
*
* @param view any MASConstraint are created with this view as the first item
*
* @return a new MASConstraintMaker
*/
- (id)initWithView:(MAS_VIEW *)view;
/**
* Calls install method on any MASConstraints which have been created by this maker
*
* @return an array of all the installed MASConstraints
*/
- (NSArray *)install;
- (MASConstraint * (^)(dispatch_block_t))group;
@end
================================================
FILE: Pods/Masonry/Masonry/MASConstraintMaker.m
================================================
//
// MASConstraintMaker.m
// Masonry
//
// Created by Jonas Budelmann on 20/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASConstraintMaker.h"
#import "MASViewConstraint.h"
#import "MASCompositeConstraint.h"
#import "MASConstraint+Private.h"
#import "MASViewAttribute.h"
#import "View+MASAdditions.h"
@interface MASConstraintMaker ()
@property (nonatomic, weak) MAS_VIEW *view;
@property (nonatomic, strong) NSMutableArray *constraints;
@end
@implementation MASConstraintMaker
- (id)initWithView:(MAS_VIEW *)view {
self = [super init];
if (!self) return nil;
self.view = view;
self.constraints = NSMutableArray.new;
return self;
}
- (NSArray *)install {
if (self.removeExisting) {
NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];
for (MASConstraint *constraint in installedConstraints) {
[constraint uninstall];
}
}
NSArray *constraints = self.constraints.copy;
for (MASConstraint *constraint in constraints) {
constraint.updateExisting = self.updateExisting;
[constraint install];
}
[self.constraints removeAllObjects];
return constraints;
}
#pragma mark - MASConstraintDelegate
- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint {
NSUInteger index = [self.constraints indexOfObject:constraint];
NSAssert(index != NSNotFound, @"Could not find constraint %@", constraint);
[self.constraints replaceObjectAtIndex:index withObject:replacementConstraint];
}
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
if ([constraint isKindOfClass:MASViewConstraint.class]) {
//replace with composite constraint
NSArray *children = @[constraint, newConstraint];
MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
compositeConstraint.delegate = self;
[self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
return compositeConstraint;
}
if (!constraint) {
newConstraint.delegate = self;
[self.constraints addObject:newConstraint];
}
return newConstraint;
}
- (MASConstraint *)addConstraintWithAttributes:(MASAttribute)attrs {
__unused MASAttribute anyAttribute = (MASAttributeLeft | MASAttributeRight | MASAttributeTop | MASAttributeBottom | MASAttributeLeading
| MASAttributeTrailing | MASAttributeWidth | MASAttributeHeight | MASAttributeCenterX
| MASAttributeCenterY | MASAttributeBaseline
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
| MASAttributeFirstBaseline | MASAttributeLastBaseline
#endif
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)
| MASAttributeLeftMargin | MASAttributeRightMargin | MASAttributeTopMargin | MASAttributeBottomMargin
| MASAttributeLeadingMargin | MASAttributeTrailingMargin | MASAttributeCenterXWithinMargins
| MASAttributeCenterYWithinMargins
#endif
);
NSAssert((attrs & anyAttribute) != 0, @"You didn't pass any attribute to make.attributes(...)");
NSMutableArray *attributes = [NSMutableArray array];
if (attrs & MASAttributeLeft) [attributes addObject:self.view.mas_left];
if (attrs & MASAttributeRight) [attributes addObject:self.view.mas_right];
if (attrs & MASAttributeTop) [attributes addObject:self.view.mas_top];
if (attrs & MASAttributeBottom) [attributes addObject:self.view.mas_bottom];
if (attrs & MASAttributeLeading) [attributes addObject:self.view.mas_leading];
if (attrs & MASAttributeTrailing) [attributes addObject:self.view.mas_trailing];
if (attrs & MASAttributeWidth) [attributes addObject:self.view.mas_width];
if (attrs & MASAttributeHeight) [attributes addObject:self.view.mas_height];
if (attrs & MASAttributeCenterX) [attributes addObject:self.view.mas_centerX];
if (attrs & MASAttributeCenterY) [attributes addObject:self.view.mas_centerY];
if (attrs & MASAttributeBaseline) [attributes addObject:self.view.mas_baseline];
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
if (attrs & MASAttributeFirstBaseline) [attributes addObject:self.view.mas_firstBaseline];
if (attrs & MASAttributeLastBaseline) [attributes addObject:self.view.mas_lastBaseline];
#endif
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)
if (attrs & MASAttributeLeftMargin) [attributes addObject:self.view.mas_leftMargin];
if (attrs & MASAttributeRightMargin) [attributes addObject:self.view.mas_rightMargin];
if (attrs & MASAttributeTopMargin) [attributes addObject:self.view.mas_topMargin];
if (attrs & MASAttributeBottomMargin) [attributes addObject:self.view.mas_bottomMargin];
if (attrs & MASAttributeLeadingMargin) [attributes addObject:self.view.mas_leadingMargin];
if (attrs & MASAttributeTrailingMargin) [attributes addObject:self.view.mas_trailingMargin];
if (attrs & MASAttributeCenterXWithinMargins) [attributes addObject:self.view.mas_centerXWithinMargins];
if (attrs & MASAttributeCenterYWithinMargins) [attributes addObject:self.view.mas_centerYWithinMargins];
#endif
NSMutableArray *children = [NSMutableArray arrayWithCapacity:attributes.count];
for (MASViewAttribute *a in attributes) {
[children addObject:[[MASViewConstraint alloc] initWithFirstViewAttribute:a]];
}
MASCompositeConstraint *constraint = [[MASCompositeConstraint alloc] initWithChildren:children];
constraint.delegate = self;
[self.constraints addObject:constraint];
return constraint;
}
#pragma mark - standard Attributes
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}
- (MASConstraint *)left {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
- (MASConstraint *)top {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
- (MASConstraint *)right {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight];
}
- (MASConstraint *)bottom {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBottom];
}
- (MASConstraint *)leading {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeading];
}
- (MASConstraint *)trailing {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTrailing];
}
- (MASConstraint *)width {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeWidth];
}
- (MASConstraint *)height {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeHeight];
}
- (MASConstraint *)centerX {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterX];
}
- (MASConstraint *)centerY {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterY];
}
- (MASConstraint *)baseline {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBaseline];
}
- (MASConstraint *(^)(MASAttribute))attributes {
return ^(MASAttribute attrs){
return [self addConstraintWithAttributes:attrs];
};
}
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
- (MASConstraint *)firstBaseline {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeFirstBaseline];
}
- (MASConstraint *)lastBaseline {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLastBaseline];
}
#endif
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)
- (MASConstraint *)leftMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeftMargin];
}
- (MASConstraint *)rightMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRightMargin];
}
- (MASConstraint *)topMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTopMargin];
}
- (MASConstraint *)bottomMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBottomMargin];
}
- (MASConstraint *)leadingMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeadingMargin];
}
- (MASConstraint *)trailingMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTrailingMargin];
}
- (MASConstraint *)centerXWithinMargins {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterXWithinMargins];
}
- (MASConstraint *)centerYWithinMargins {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterYWithinMargins];
}
#endif
#pragma mark - composite Attributes
- (MASConstraint *)edges {
return [self addConstraintWithAttributes:MASAttributeTop | MASAttributeLeft | MASAttributeRight | MASAttributeBottom];
}
- (MASConstraint *)size {
return [self addConstraintWithAttributes:MASAttributeWidth | MASAttributeHeight];
}
- (MASConstraint *)center {
return [self addConstraintWithAttributes:MASAttributeCenterX | MASAttributeCenterY];
}
#pragma mark - grouping
- (MASConstraint *(^)(dispatch_block_t group))group {
return ^id(dispatch_block_t group) {
NSInteger previousCount = self.constraints.count;
group();
NSArray *children = [self.constraints subarrayWithRange:NSMakeRange(previousCount, self.constraints.count - previousCount)];
MASCompositeConstraint *constraint = [[MASCompositeConstraint alloc] initWithChildren:children];
constraint.delegate = self;
return constraint;
};
}
@end
================================================
FILE: Pods/Masonry/Masonry/MASLayoutConstraint.h
================================================
//
// MASLayoutConstraint.h
// Masonry
//
// Created by Jonas Budelmann on 3/08/13.
// Copyright (c) 2013 Jonas Budelmann. All rights reserved.
//
#import "MASUtilities.h"
/**
* When you are debugging or printing the constraints attached to a view this subclass
* makes it easier to identify which constraints have been created via Masonry
*/
@interface MASLayoutConstraint : NSLayoutConstraint
/**
* a key to associate with this constraint
*/
@property (nonatomic, strong) id mas_key;
@end
================================================
FILE: Pods/Masonry/Masonry/MASLayoutConstraint.m
================================================
//
// MASLayoutConstraint.m
// Masonry
//
// Created by Jonas Budelmann on 3/08/13.
// Copyright (c) 2013 Jonas Budelmann. All rights reserved.
//
#import "MASLayoutConstraint.h"
@implementation MASLayoutConstraint
@end
================================================
FILE: Pods/Masonry/Masonry/MASUtilities.h
================================================
//
// MASUtilities.h
// Masonry
//
// Created by Jonas Budelmann on 19/08/13.
// Copyright (c) 2013 Jonas Budelmann. All rights reserved.
//
#import
#if TARGET_OS_IPHONE || TARGET_OS_TV
#import
#define MAS_VIEW UIView
#define MAS_VIEW_CONTROLLER UIViewController
#define MASEdgeInsets UIEdgeInsets
typedef UILayoutPriority MASLayoutPriority;
static const MASLayoutPriority MASLayoutPriorityRequired = UILayoutPriorityRequired;
static const MASLayoutPriority MASLayoutPriorityDefaultHigh = UILayoutPriorityDefaultHigh;
static const MASLayoutPriority MASLayoutPriorityDefaultMedium = 500;
static const MASLayoutPriority MASLayoutPriorityDefaultLow = UILayoutPriorityDefaultLow;
static const MASLayoutPriority MASLayoutPriorityFittingSizeLevel = UILayoutPriorityFittingSizeLevel;
#elif TARGET_OS_MAC
#import
#define MAS_VIEW NSView
#define MASEdgeInsets NSEdgeInsets
typedef NSLayoutPriority MASLayoutPriority;
static const MASLayoutPriority MASLayoutPriorityRequired = NSLayoutPriorityRequired;
static const MASLayoutPriority MASLayoutPriorityDefaultHigh = NSLayoutPriorityDefaultHigh;
static const MASLayoutPriority MASLayoutPriorityDragThatCanResizeWindow = NSLayoutPriorityDragThatCanResizeWindow;
static const MASLayoutPriority MASLayoutPriorityDefaultMedium = 501;
static const MASLayoutPriority MASLayoutPriorityWindowSizeStayPut = NSLayoutPriorityWindowSizeStayPut;
static const MASLayoutPriority MASLayoutPriorityDragThatCannotResizeWindow = NSLayoutPriorityDragThatCannotResizeWindow;
static const MASLayoutPriority MASLayoutPriorityDefaultLow = NSLayoutPriorityDefaultLow;
static const MASLayoutPriority MASLayoutPriorityFittingSizeCompression = NSLayoutPriorityFittingSizeCompression;
#endif
/**
* Allows you to attach keys to objects matching the variable names passed.
*
* view1.mas_key = @"view1", view2.mas_key = @"view2";
*
* is equivalent to:
*
* MASAttachKeys(view1, view2);
*/
#define MASAttachKeys(...) \
{ \
NSDictionary *keyPairs = NSDictionaryOfVariableBindings(__VA_ARGS__); \
for (id key in keyPairs.allKeys) { \
id obj = keyPairs[key]; \
NSAssert([obj respondsToSelector:@selector(setMas_key:)], \
@"Cannot attach mas_key to %@", obj); \
[obj setMas_key:key]; \
} \
}
/**
* Used to create object hashes
* Based on http://www.mikeash.com/pyblog/friday-qa-2010-06-18-implementing-equality-and-hashing.html
*/
#define MAS_NSUINT_BIT (CHAR_BIT * sizeof(NSUInteger))
#define MAS_NSUINTROTATE(val, howmuch) ((((NSUInteger)val) << howmuch) | (((NSUInteger)val) >> (MAS_NSUINT_BIT - howmuch)))
/**
* Given a scalar or struct value, wraps it in NSValue
* Based on EXPObjectify: https://github.com/specta/expecta
*/
static inline id _MASBoxValue(const char *type, ...) {
va_list v;
va_start(v, type);
id obj = nil;
if (strcmp(type, @encode(id)) == 0) {
id actual = va_arg(v, id);
obj = actual;
} else if (strcmp(type, @encode(CGPoint)) == 0) {
CGPoint actual = (CGPoint)va_arg(v, CGPoint);
obj = [NSValue value:&actual withObjCType:type];
} else if (strcmp(type, @encode(CGSize)) == 0) {
CGSize actual = (CGSize)va_arg(v, CGSize);
obj = [NSValue value:&actual withObjCType:type];
} else if (strcmp(type, @encode(MASEdgeInsets)) == 0) {
MASEdgeInsets actual = (MASEdgeInsets)va_arg(v, MASEdgeInsets);
obj = [NSValue value:&actual withObjCType:type];
} else if (strcmp(type, @encode(double)) == 0) {
double actual = (double)va_arg(v, double);
obj = [NSNumber numberWithDouble:actual];
} else if (strcmp(type, @encode(float)) == 0) {
float actual = (float)va_arg(v, double);
obj = [NSNumber numberWithFloat:actual];
} else if (strcmp(type, @encode(int)) == 0) {
int actual = (int)va_arg(v, int);
obj = [NSNumber numberWithInt:actual];
} else if (strcmp(type, @encode(long)) == 0) {
long actual = (long)va_arg(v, long);
obj = [NSNumber numberWithLong:actual];
} else if (strcmp(type, @encode(long long)) == 0) {
long long actual = (long long)va_arg(v, long long);
obj = [NSNumber numberWithLongLong:actual];
} else if (strcmp(type, @encode(short)) == 0) {
short actual = (short)va_arg(v, int);
obj = [NSNumber numberWithShort:actual];
} else if (strcmp(type, @encode(char)) == 0) {
char actual = (char)va_arg(v, int);
obj = [NSNumber numberWithChar:actual];
} else if (strcmp(type, @encode(bool)) == 0) {
bool actual = (bool)va_arg(v, int);
obj = [NSNumber numberWithBool:actual];
} else if (strcmp(type, @encode(unsigned char)) == 0) {
unsigned char actual = (unsigned char)va_arg(v, unsigned int);
obj = [NSNumber numberWithUnsignedChar:actual];
} else if (strcmp(type, @encode(unsigned int)) == 0) {
unsigned int actual = (unsigned int)va_arg(v, unsigned int);
obj = [NSNumber numberWithUnsignedInt:actual];
} else if (strcmp(type, @encode(unsigned long)) == 0) {
unsigned long actual = (unsigned long)va_arg(v, unsigned long);
obj = [NSNumber numberWithUnsignedLong:actual];
} else if (strcmp(type, @encode(unsigned long long)) == 0) {
unsigned long long actual = (unsigned long long)va_arg(v, unsigned long long);
obj = [NSNumber numberWithUnsignedLongLong:actual];
} else if (strcmp(type, @encode(unsigned short)) == 0) {
unsigned short actual = (unsigned short)va_arg(v, unsigned int);
obj = [NSNumber numberWithUnsignedShort:actual];
}
va_end(v);
return obj;
}
#define MASBoxValue(value) _MASBoxValue(@encode(__typeof__((value))), (value))
================================================
FILE: Pods/Masonry/Masonry/MASViewAttribute.h
================================================
//
// MASViewAttribute.h
// Masonry
//
// Created by Jonas Budelmann on 21/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASUtilities.h"
/**
* An immutable tuple which stores the view and the related NSLayoutAttribute.
* Describes part of either the left or right hand side of a constraint equation
*/
@interface MASViewAttribute : NSObject
/**
* The view which the reciever relates to. Can be nil if item is not a view.
*/
@property (nonatomic, weak, readonly) MAS_VIEW *view;
/**
* The item which the reciever relates to.
*/
@property (nonatomic, weak, readonly) id item;
/**
* The attribute which the reciever relates to
*/
@property (nonatomic, assign, readonly) NSLayoutAttribute layoutAttribute;
/**
* Convenience initializer.
*/
- (id)initWithView:(MAS_VIEW *)view layoutAttribute:(NSLayoutAttribute)layoutAttribute;
/**
* The designated initializer.
*/
- (id)initWithView:(MAS_VIEW *)view item:(id)item layoutAttribute:(NSLayoutAttribute)layoutAttribute;
/**
* Determine whether the layoutAttribute is a size attribute
*
* @return YES if layoutAttribute is equal to NSLayoutAttributeWidth or NSLayoutAttributeHeight
*/
- (BOOL)isSizeAttribute;
@end
================================================
FILE: Pods/Masonry/Masonry/MASViewAttribute.m
================================================
//
// MASViewAttribute.m
// Masonry
//
// Created by Jonas Budelmann on 21/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASViewAttribute.h"
@implementation MASViewAttribute
- (id)initWithView:(MAS_VIEW *)view layoutAttribute:(NSLayoutAttribute)layoutAttribute {
self = [self initWithView:view item:view layoutAttribute:layoutAttribute];
return self;
}
- (id)initWithView:(MAS_VIEW *)view item:(id)item layoutAttribute:(NSLayoutAttribute)layoutAttribute {
self = [super init];
if (!self) return nil;
_view = view;
_item = item;
_layoutAttribute = layoutAttribute;
return self;
}
- (BOOL)isSizeAttribute {
return self.layoutAttribute == NSLayoutAttributeWidth
|| self.layoutAttribute == NSLayoutAttributeHeight;
}
- (BOOL)isEqual:(MASViewAttribute *)viewAttribute {
if ([viewAttribute isKindOfClass:self.class]) {
return self.view == viewAttribute.view
&& self.layoutAttribute == viewAttribute.layoutAttribute;
}
return [super isEqual:viewAttribute];
}
- (NSUInteger)hash {
return MAS_NSUINTROTATE([self.view hash], MAS_NSUINT_BIT / 2) ^ self.layoutAttribute;
}
@end
================================================
FILE: Pods/Masonry/Masonry/MASViewConstraint.h
================================================
//
// MASViewConstraint.h
// Masonry
//
// Created by Jonas Budelmann on 20/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASViewAttribute.h"
#import "MASConstraint.h"
#import "MASLayoutConstraint.h"
#import "MASUtilities.h"
/**
* A single constraint.
* Contains the attributes neccessary for creating a NSLayoutConstraint and adding it to the appropriate view
*/
@interface MASViewConstraint : MASConstraint
/**
* First item/view and first attribute of the NSLayoutConstraint
*/
@property (nonatomic, strong, readonly) MASViewAttribute *firstViewAttribute;
/**
* Second item/view and second attribute of the NSLayoutConstraint
*/
@property (nonatomic, strong, readonly) MASViewAttribute *secondViewAttribute;
/**
* initialises the MASViewConstraint with the first part of the equation
*
* @param firstViewAttribute view.mas_left, view.mas_width etc.
*
* @return a new view constraint
*/
- (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute;
/**
* Returns all MASViewConstraints installed with this view as a first item.
*
* @param view A view to retrieve constraints for.
*
* @return An array of MASViewConstraints.
*/
+ (NSArray *)installedConstraintsForView:(MAS_VIEW *)view;
@end
================================================
FILE: Pods/Masonry/Masonry/MASViewConstraint.m
================================================
//
// MASViewConstraint.m
// Masonry
//
// Created by Jonas Budelmann on 20/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASViewConstraint.h"
#import "MASConstraint+Private.h"
#import "MASCompositeConstraint.h"
#import "MASLayoutConstraint.h"
#import "View+MASAdditions.h"
#import
@interface MAS_VIEW (MASConstraints)
@property (nonatomic, readonly) NSMutableSet *mas_installedConstraints;
@end
@implementation MAS_VIEW (MASConstraints)
static char kInstalledConstraintsKey;
- (NSMutableSet *)mas_installedConstraints {
NSMutableSet *constraints = objc_getAssociatedObject(self, &kInstalledConstraintsKey);
if (!constraints) {
constraints = [NSMutableSet set];
objc_setAssociatedObject(self, &kInstalledConstraintsKey, constraints, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return constraints;
}
@end
@interface MASViewConstraint ()
@property (nonatomic, strong, readwrite) MASViewAttribute *secondViewAttribute;
@property (nonatomic, weak) MAS_VIEW *installedView;
@property (nonatomic, weak) MASLayoutConstraint *layoutConstraint;
@property (nonatomic, assign) NSLayoutRelation layoutRelation;
@property (nonatomic, assign) MASLayoutPriority layoutPriority;
@property (nonatomic, assign) CGFloat layoutMultiplier;
@property (nonatomic, assign) CGFloat layoutConstant;
@property (nonatomic, assign) BOOL hasLayoutRelation;
@property (nonatomic, strong) id mas_key;
@property (nonatomic, assign) BOOL useAnimator;
@end
@implementation MASViewConstraint
- (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute {
self = [super init];
if (!self) return nil;
_firstViewAttribute = firstViewAttribute;
self.layoutPriority = MASLayoutPriorityRequired;
self.layoutMultiplier = 1;
return self;
}
#pragma mark - NSCoping
- (id)copyWithZone:(NSZone __unused *)zone {
MASViewConstraint *constraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:self.firstViewAttribute];
constraint.layoutConstant = self.layoutConstant;
constraint.layoutRelation = self.layoutRelation;
constraint.layoutPriority = self.layoutPriority;
constraint.layoutMultiplier = self.layoutMultiplier;
constraint.delegate = self.delegate;
return constraint;
}
#pragma mark - Public
+ (NSArray *)installedConstraintsForView:(MAS_VIEW *)view {
return [view.mas_installedConstraints allObjects];
}
#pragma mark - Private
- (void)setLayoutConstant:(CGFloat)layoutConstant {
_layoutConstant = layoutConstant;
#if TARGET_OS_MAC && !(TARGET_OS_IPHONE || TARGET_OS_TV)
if (self.useAnimator) {
[self.layoutConstraint.animator setConstant:layoutConstant];
} else {
self.layoutConstraint.constant = layoutConstant;
}
#else
self.layoutConstraint.constant = layoutConstant;
#endif
}
- (void)setLayoutRelation:(NSLayoutRelation)layoutRelation {
_layoutRelation = layoutRelation;
self.hasLayoutRelation = YES;
}
- (BOOL)supportsActiveProperty {
return [self.layoutConstraint respondsToSelector:@selector(isActive)];
}
- (BOOL)isActive {
BOOL active = YES;
if ([self supportsActiveProperty]) {
active = [self.layoutConstraint isActive];
}
return active;
}
- (BOOL)hasBeenInstalled {
return (self.layoutConstraint != nil) && [self isActive];
}
- (void)setSecondViewAttribute:(id)secondViewAttribute {
if ([secondViewAttribute isKindOfClass:NSValue.class]) {
[self setLayoutConstantWithValue:secondViewAttribute];
} else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) {
_secondViewAttribute = [[MASViewAttribute alloc] initWithView:secondViewAttribute layoutAttribute:self.firstViewAttribute.layoutAttribute];
} else if ([secondViewAttribute isKindOfClass:MASViewAttribute.class]) {
_secondViewAttribute = secondViewAttribute;
} else {
NSAssert(NO, @"attempting to add unsupported attribute: %@", secondViewAttribute);
}
}
#pragma mark - NSLayoutConstraint multiplier proxies
- (MASConstraint * (^)(CGFloat))multipliedBy {
return ^id(CGFloat multiplier) {
NSAssert(!self.hasBeenInstalled,
@"Cannot modify constraint multiplier after it has been installed");
self.layoutMultiplier = multiplier;
return self;
};
}
- (MASConstraint * (^)(CGFloat))dividedBy {
return ^id(CGFloat divider) {
NSAssert(!self.hasBeenInstalled,
@"Cannot modify constraint multiplier after it has been installed");
self.layoutMultiplier = 1.0/divider;
return self;
};
}
#pragma mark - MASLayoutPriority proxy
- (MASConstraint * (^)(MASLayoutPriority))priority {
return ^id(MASLayoutPriority priority) {
NSAssert(!self.hasBeenInstalled,
@"Cannot modify constraint priority after it has been installed");
self.layoutPriority = priority;
return self;
};
}
#pragma mark - NSLayoutRelation proxy
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
return ^id(id attribute, NSLayoutRelation relation) {
if ([attribute isKindOfClass:NSArray.class]) {
NSAssert(!self.hasLayoutRelation, @"Redefinition of constraint relation");
NSMutableArray *children = NSMutableArray.new;
for (id attr in attribute) {
MASViewConstraint *viewConstraint = [self copy];
viewConstraint.layoutRelation = relation;
viewConstraint.secondViewAttribute = attr;
[children addObject:viewConstraint];
}
MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
compositeConstraint.delegate = self.delegate;
[self.delegate constraint:self shouldBeReplacedWithConstraint:compositeConstraint];
return compositeConstraint;
} else {
NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
self.layoutRelation = relation;
self.secondViewAttribute = attribute;
return self;
}
};
}
#pragma mark - Semantic properties
- (MASConstraint *)with {
return self;
}
- (MASConstraint *)and {
return self;
}
#pragma mark - attribute chaining
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
#pragma mark - Animator proxy
#if TARGET_OS_MAC && !(TARGET_OS_IPHONE || TARGET_OS_TV)
- (MASConstraint *)animator {
self.useAnimator = YES;
return self;
}
#endif
#pragma mark - debug helpers
- (MASConstraint * (^)(id))key {
return ^id(id key) {
self.mas_key = key;
return self;
};
}
#pragma mark - NSLayoutConstraint constant setters
- (void)setInsets:(MASEdgeInsets)insets {
NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;
switch (layoutAttribute) {
case NSLayoutAttributeLeft:
case NSLayoutAttributeLeading:
self.layoutConstant = insets.left;
break;
case NSLayoutAttributeTop:
self.layoutConstant = insets.top;
break;
case NSLayoutAttributeBottom:
self.layoutConstant = -insets.bottom;
break;
case NSLayoutAttributeRight:
case NSLayoutAttributeTrailing:
self.layoutConstant = -insets.right;
break;
default:
break;
}
}
- (void)setInset:(CGFloat)inset {
[self setInsets:(MASEdgeInsets){.top = inset, .left = inset, .bottom = inset, .right = inset}];
}
- (void)setOffset:(CGFloat)offset {
self.layoutConstant = offset;
}
- (void)setSizeOffset:(CGSize)sizeOffset {
NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;
switch (layoutAttribute) {
case NSLayoutAttributeWidth:
self.layoutConstant = sizeOffset.width;
break;
case NSLayoutAttributeHeight:
self.layoutConstant = sizeOffset.height;
break;
default:
break;
}
}
- (void)setCenterOffset:(CGPoint)centerOffset {
NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;
switch (layoutAttribute) {
case NSLayoutAttributeCenterX:
self.layoutConstant = centerOffset.x;
break;
case NSLayoutAttributeCenterY:
self.layoutConstant = centerOffset.y;
break;
default:
break;
}
}
#pragma mark - MASConstraint
- (void)activate {
[self install];
}
- (void)deactivate {
[self uninstall];
}
- (void)install {
if (self.hasBeenInstalled) {
return;
}
if ([self supportsActiveProperty] && self.layoutConstraint) {
self.layoutConstraint.active = YES;
[self.firstViewAttribute.view.mas_installedConstraints addObject:self];
return;
}
MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;
// alignment attributes must have a secondViewAttribute
// therefore we assume that is refering to superview
// eg make.left.equalTo(@10)
if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
secondLayoutItem = self.firstViewAttribute.view.superview;
secondLayoutAttribute = firstLayoutAttribute;
}
MASLayoutConstraint *layoutConstraint
= [MASLayoutConstraint constraintWithItem:firstLayoutItem
attribute:firstLayoutAttribute
relatedBy:self.layoutRelation
toItem:secondLayoutItem
attribute:secondLayoutAttribute
multiplier:self.layoutMultiplier
constant:self.layoutConstant];
layoutConstraint.priority = self.layoutPriority;
layoutConstraint.mas_key = self.mas_key;
if (self.secondViewAttribute.view) {
MAS_VIEW *closestCommonSuperview = [self.firstViewAttribute.view mas_closestCommonSuperview:self.secondViewAttribute.view];
NSAssert(closestCommonSuperview,
@"couldn't find a common superview for %@ and %@",
self.firstViewAttribute.view, self.secondViewAttribute.view);
self.installedView = closestCommonSuperview;
} else if (self.firstViewAttribute.isSizeAttribute) {
self.installedView = self.firstViewAttribute.view;
} else {
self.installedView = self.firstViewAttribute.view.superview;
}
MASLayoutConstraint *existingConstraint = nil;
if (self.updateExisting) {
existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
}
if (existingConstraint) {
// just update the constant
existingConstraint.constant = layoutConstraint.constant;
self.layoutConstraint = existingConstraint;
} else {
[self.installedView addConstraint:layoutConstraint];
self.layoutConstraint = layoutConstraint;
[firstLayoutItem.mas_installedConstraints addObject:self];
}
}
- (MASLayoutConstraint *)layoutConstraintSimilarTo:(MASLayoutConstraint *)layoutConstraint {
// check if any constraints are the same apart from the only mutable property constant
// go through constraints in reverse as we do not want to match auto-resizing or interface builder constraints
// and they are likely to be added first.
for (NSLayoutConstraint *existingConstraint in self.installedView.constraints.reverseObjectEnumerator) {
if (![existingConstraint isKindOfClass:MASLayoutConstraint.class]) continue;
if (existingConstraint.firstItem != layoutConstraint.firstItem) continue;
if (existingConstraint.secondItem != layoutConstraint.secondItem) continue;
if (existingConstraint.firstAttribute != layoutConstraint.firstAttribute) continue;
if (existingConstraint.secondAttribute != layoutConstraint.secondAttribute) continue;
if (existingConstraint.relation != layoutConstraint.relation) continue;
if (existingConstraint.multiplier != layoutConstraint.multiplier) continue;
if (existingConstraint.priority != layoutConstraint.priority) continue;
return (id)existingConstraint;
}
return nil;
}
- (void)uninstall {
if ([self supportsActiveProperty]) {
self.layoutConstraint.active = NO;
[self.firstViewAttribute.view.mas_installedConstraints removeObject:self];
return;
}
[self.installedView removeConstraint:self.layoutConstraint];
self.layoutConstraint = nil;
self.installedView = nil;
[self.firstViewAttribute.view.mas_installedConstraints removeObject:self];
}
@end
================================================
FILE: Pods/Masonry/Masonry/Masonry.h
================================================
//
// Masonry.h
// Masonry
//
// Created by Jonas Budelmann on 20/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import
//! Project version number for Masonry.
FOUNDATION_EXPORT double MasonryVersionNumber;
//! Project version string for Masonry.
FOUNDATION_EXPORT const unsigned char MasonryVersionString[];
#import "MASUtilities.h"
#import "View+MASAdditions.h"
#import "View+MASShorthandAdditions.h"
#import "ViewController+MASAdditions.h"
#import "NSArray+MASAdditions.h"
#import "NSArray+MASShorthandAdditions.h"
#import "MASConstraint.h"
#import "MASCompositeConstraint.h"
#import "MASViewAttribute.h"
#import "MASViewConstraint.h"
#import "MASConstraintMaker.h"
#import "MASLayoutConstraint.h"
#import "NSLayoutConstraint+MASDebugAdditions.h"
================================================
FILE: Pods/Masonry/Masonry/NSArray+MASAdditions.h
================================================
//
// NSArray+MASAdditions.h
//
//
// Created by Daniel Hammond on 11/26/13.
//
//
#import "MASUtilities.h"
#import "MASConstraintMaker.h"
#import "MASViewAttribute.h"
typedef NS_ENUM(NSUInteger, MASAxisType) {
MASAxisTypeHorizontal,
MASAxisTypeVertical
};
@interface NSArray (MASAdditions)
/**
* Creates a MASConstraintMaker with each view in the callee.
* Any constraints defined are added to the view or the appropriate superview once the block has finished executing on each view
*
* @param block scope within which you can build up the constraints which you wish to apply to each view.
*
* @return Array of created MASConstraints
*/
- (NSArray *)mas_makeConstraints:(void (NS_NOESCAPE ^)(MASConstraintMaker *make))block;
/**
* Creates a MASConstraintMaker with each view in the callee.
* Any constraints defined are added to each view or the appropriate superview once the block has finished executing on each view.
* If an existing constraint exists then it will be updated instead.
*
* @param block scope within which you can build up the constraints which you wish to apply to each view.
*
* @return Array of created/updated MASConstraints
*/
- (NSArray *)mas_updateConstraints:(void (NS_NOESCAPE ^)(MASConstraintMaker *make))block;
/**
* Creates a MASConstraintMaker with each view in the callee.
* Any constraints defined are added to each view or the appropriate superview once the block has finished executing on each view.
* All constraints previously installed for the views will be removed.
*
* @param block scope within which you can build up the constraints which you wish to apply to each view.
*
* @return Array of created/updated MASConstraints
*/
- (NSArray *)mas_remakeConstraints:(void (NS_NOESCAPE ^)(MASConstraintMaker *make))block;
/**
* distribute with fixed spacing
*
* @param axisType which axis to distribute items along
* @param fixedSpacing the spacing between each item
* @param leadSpacing the spacing before the first item and the container
* @param tailSpacing the spacing after the last item and the container
*/
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedSpacing:(CGFloat)fixedSpacing leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing;
/**
* distribute with fixed item size
*
* @param axisType which axis to distribute items along
* @param fixedItemLength the fixed length of each item
* @param leadSpacing the spacing before the first item and the container
* @param tailSpacing the spacing after the last item and the container
*/
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedItemLength:(CGFloat)fixedItemLength leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing;
@end
================================================
FILE: Pods/Masonry/Masonry/NSArray+MASAdditions.m
================================================
//
// NSArray+MASAdditions.m
//
//
// Created by Daniel Hammond on 11/26/13.
//
//
#import "NSArray+MASAdditions.h"
#import "View+MASAdditions.h"
@implementation NSArray (MASAdditions)
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block {
NSMutableArray *constraints = [NSMutableArray array];
for (MAS_VIEW *view in self) {
NSAssert([view isKindOfClass:[MAS_VIEW class]], @"All objects in the array must be views");
[constraints addObjectsFromArray:[view mas_makeConstraints:block]];
}
return constraints;
}
- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make))block {
NSMutableArray *constraints = [NSMutableArray array];
for (MAS_VIEW *view in self) {
NSAssert([view isKindOfClass:[MAS_VIEW class]], @"All objects in the array must be views");
[constraints addObjectsFromArray:[view mas_updateConstraints:block]];
}
return constraints;
}
- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block {
NSMutableArray *constraints = [NSMutableArray array];
for (MAS_VIEW *view in self) {
NSAssert([view isKindOfClass:[MAS_VIEW class]], @"All objects in the array must be views");
[constraints addObjectsFromArray:[view mas_remakeConstraints:block]];
}
return constraints;
}
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedSpacing:(CGFloat)fixedSpacing leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing {
if (self.count < 2) {
NSAssert(self.count>1,@"views to distribute need to bigger than one");
return;
}
MAS_VIEW *tempSuperView = [self mas_commonSuperviewOfViews];
if (axisType == MASAxisTypeHorizontal) {
MAS_VIEW *prev;
for (int i = 0; i < self.count; i++) {
MAS_VIEW *v = self[i];
[v mas_makeConstraints:^(MASConstraintMaker *make) {
if (prev) {
make.width.equalTo(prev);
make.left.equalTo(prev.mas_right).offset(fixedSpacing);
if (i == self.count - 1) {//last one
make.right.equalTo(tempSuperView).offset(-tailSpacing);
}
}
else {//first one
make.left.equalTo(tempSuperView).offset(leadSpacing);
}
}];
prev = v;
}
}
else {
MAS_VIEW *prev;
for (int i = 0; i < self.count; i++) {
MAS_VIEW *v = self[i];
[v mas_makeConstraints:^(MASConstraintMaker *make) {
if (prev) {
make.height.equalTo(prev);
make.top.equalTo(prev.mas_bottom).offset(fixedSpacing);
if (i == self.count - 1) {//last one
make.bottom.equalTo(tempSuperView).offset(-tailSpacing);
}
}
else {//first one
make.top.equalTo(tempSuperView).offset(leadSpacing);
}
}];
prev = v;
}
}
}
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedItemLength:(CGFloat)fixedItemLength leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing {
if (self.count < 2) {
NSAssert(self.count>1,@"views to distribute need to bigger than one");
return;
}
MAS_VIEW *tempSuperView = [self mas_commonSuperviewOfViews];
if (axisType == MASAxisTypeHorizontal) {
MAS_VIEW *prev;
for (int i = 0; i < self.count; i++) {
MAS_VIEW *v = self[i];
[v mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(@(fixedItemLength));
if (prev) {
if (i == self.count - 1) {//last one
make.right.equalTo(tempSuperView).offset(-tailSpacing);
}
else {
CGFloat offset = (1-(i/((CGFloat)self.count-1)))*(fixedItemLength+leadSpacing)-i*tailSpacing/(((CGFloat)self.count-1));
make.right.equalTo(tempSuperView).multipliedBy(i/((CGFloat)self.count-1)).with.offset(offset);
}
}
else {//first one
make.left.equalTo(tempSuperView).offset(leadSpacing);
}
}];
prev = v;
}
}
else {
MAS_VIEW *prev;
for (int i = 0; i < self.count; i++) {
MAS_VIEW *v = self[i];
[v mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.equalTo(@(fixedItemLength));
if (prev) {
if (i == self.count - 1) {//last one
make.bottom.equalTo(tempSuperView).offset(-tailSpacing);
}
else {
CGFloat offset = (1-(i/((CGFloat)self.count-1)))*(fixedItemLength+leadSpacing)-i*tailSpacing/(((CGFloat)self.count-1));
make.bottom.equalTo(tempSuperView).multipliedBy(i/((CGFloat)self.count-1)).with.offset(offset);
}
}
else {//first one
make.top.equalTo(tempSuperView).offset(leadSpacing);
}
}];
prev = v;
}
}
}
- (MAS_VIEW *)mas_commonSuperviewOfViews
{
MAS_VIEW *commonSuperview = nil;
MAS_VIEW *previousView = nil;
for (id object in self) {
if ([object isKindOfClass:[MAS_VIEW class]]) {
MAS_VIEW *view = (MAS_VIEW *)object;
if (previousView) {
commonSuperview = [view mas_closestCommonSuperview:commonSuperview];
} else {
commonSuperview = view;
}
previousView = view;
}
}
NSAssert(commonSuperview, @"Can't constrain views that do not share a common superview. Make sure that all the views in this array have been added into the same view hierarchy.");
return commonSuperview;
}
@end
================================================
FILE: Pods/Masonry/Masonry/NSArray+MASShorthandAdditions.h
================================================
//
// NSArray+MASShorthandAdditions.h
// Masonry
//
// Created by Jonas Budelmann on 22/07/13.
// Copyright (c) 2013 Jonas Budelmann. All rights reserved.
//
#import "NSArray+MASAdditions.h"
#ifdef MAS_SHORTHAND
/**
* Shorthand array additions without the 'mas_' prefixes,
* only enabled if MAS_SHORTHAND is defined
*/
@interface NSArray (MASShorthandAdditions)
- (NSArray *)makeConstraints:(void(^)(MASConstraintMaker *make))block;
- (NSArray *)updateConstraints:(void(^)(MASConstraintMaker *make))block;
- (NSArray *)remakeConstraints:(void(^)(MASConstraintMaker *make))block;
@end
@implementation NSArray (MASShorthandAdditions)
- (NSArray *)makeConstraints:(void(^)(MASConstraintMaker *))block {
return [self mas_makeConstraints:block];
}
- (NSArray *)updateConstraints:(void(^)(MASConstraintMaker *))block {
return [self mas_updateConstraints:block];
}
- (NSArray *)remakeConstraints:(void(^)(MASConstraintMaker *))block {
return [self mas_remakeConstraints:block];
}
@end
#endif
================================================
FILE: Pods/Masonry/Masonry/NSLayoutConstraint+MASDebugAdditions.h
================================================
//
// NSLayoutConstraint+MASDebugAdditions.h
// Masonry
//
// Created by Jonas Budelmann on 3/08/13.
// Copyright (c) 2013 Jonas Budelmann. All rights reserved.
//
#import "MASUtilities.h"
/**
* makes debug and log output of NSLayoutConstraints more readable
*/
@interface NSLayoutConstraint (MASDebugAdditions)
@end
================================================
FILE: Pods/Masonry/Masonry/NSLayoutConstraint+MASDebugAdditions.m
================================================
//
// NSLayoutConstraint+MASDebugAdditions.m
// Masonry
//
// Created by Jonas Budelmann on 3/08/13.
// Copyright (c) 2013 Jonas Budelmann. All rights reserved.
//
#import "NSLayoutConstraint+MASDebugAdditions.h"
#import "MASConstraint.h"
#import "MASLayoutConstraint.h"
@implementation NSLayoutConstraint (MASDebugAdditions)
#pragma mark - description maps
+ (NSDictionary *)layoutRelationDescriptionsByValue {
static dispatch_once_t once;
static NSDictionary *descriptionMap;
dispatch_once(&once, ^{
descriptionMap = @{
@(NSLayoutRelationEqual) : @"==",
@(NSLayoutRelationGreaterThanOrEqual) : @">=",
@(NSLayoutRelationLessThanOrEqual) : @"<=",
};
});
return descriptionMap;
}
+ (NSDictionary *)layoutAttributeDescriptionsByValue {
static dispatch_once_t once;
static NSDictionary *descriptionMap;
dispatch_once(&once, ^{
descriptionMap = @{
@(NSLayoutAttributeTop) : @"top",
@(NSLayoutAttributeLeft) : @"left",
@(NSLayoutAttributeBottom) : @"bottom",
@(NSLayoutAttributeRight) : @"right",
@(NSLayoutAttributeLeading) : @"leading",
@(NSLayoutAttributeTrailing) : @"trailing",
@(NSLayoutAttributeWidth) : @"width",
@(NSLayoutAttributeHeight) : @"height",
@(NSLayoutAttributeCenterX) : @"centerX",
@(NSLayoutAttributeCenterY) : @"centerY",
@(NSLayoutAttributeBaseline) : @"baseline",
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
@(NSLayoutAttributeFirstBaseline) : @"firstBaseline",
@(NSLayoutAttributeLastBaseline) : @"lastBaseline",
#endif
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)
@(NSLayoutAttributeLeftMargin) : @"leftMargin",
@(NSLayoutAttributeRightMargin) : @"rightMargin",
@(NSLayoutAttributeTopMargin) : @"topMargin",
@(NSLayoutAttributeBottomMargin) : @"bottomMargin",
@(NSLayoutAttributeLeadingMargin) : @"leadingMargin",
@(NSLayoutAttributeTrailingMargin) : @"trailingMargin",
@(NSLayoutAttributeCenterXWithinMargins) : @"centerXWithinMargins",
@(NSLayoutAttributeCenterYWithinMargins) : @"centerYWithinMargins",
#endif
};
});
return descriptionMap;
}
+ (NSDictionary *)layoutPriorityDescriptionsByValue {
static dispatch_once_t once;
static NSDictionary *descriptionMap;
dispatch_once(&once, ^{
#if TARGET_OS_IPHONE || TARGET_OS_TV
descriptionMap = @{
@(MASLayoutPriorityDefaultHigh) : @"high",
@(MASLayoutPriorityDefaultLow) : @"low",
@(MASLayoutPriorityDefaultMedium) : @"medium",
@(MASLayoutPriorityRequired) : @"required",
@(MASLayoutPriorityFittingSizeLevel) : @"fitting size",
};
#elif TARGET_OS_MAC
descriptionMap = @{
@(MASLayoutPriorityDefaultHigh) : @"high",
@(MASLayoutPriorityDragThatCanResizeWindow) : @"drag can resize window",
@(MASLayoutPriorityDefaultMedium) : @"medium",
@(MASLayoutPriorityWindowSizeStayPut) : @"window size stay put",
@(MASLayoutPriorityDragThatCannotResizeWindow) : @"drag cannot resize window",
@(MASLayoutPriorityDefaultLow) : @"low",
@(MASLayoutPriorityFittingSizeCompression) : @"fitting size",
@(MASLayoutPriorityRequired) : @"required",
};
#endif
});
return descriptionMap;
}
#pragma mark - description override
+ (NSString *)descriptionForObject:(id)obj {
if ([obj respondsToSelector:@selector(mas_key)] && [obj mas_key]) {
return [NSString stringWithFormat:@"%@:%@", [obj class], [obj mas_key]];
}
return [NSString stringWithFormat:@"%@:%p", [obj class], obj];
}
- (NSString *)description {
NSMutableString *description = [[NSMutableString alloc] initWithString:@"<"];
[description appendString:[self.class descriptionForObject:self]];
[description appendFormat:@" %@", [self.class descriptionForObject:self.firstItem]];
if (self.firstAttribute != NSLayoutAttributeNotAnAttribute) {
[description appendFormat:@".%@", self.class.layoutAttributeDescriptionsByValue[@(self.firstAttribute)]];
}
[description appendFormat:@" %@", self.class.layoutRelationDescriptionsByValue[@(self.relation)]];
if (self.secondItem) {
[description appendFormat:@" %@", [self.class descriptionForObject:self.secondItem]];
}
if (self.secondAttribute != NSLayoutAttributeNotAnAttribute) {
[description appendFormat:@".%@", self.class.layoutAttributeDescriptionsByValue[@(self.secondAttribute)]];
}
if (self.multiplier != 1) {
[description appendFormat:@" * %g", self.multiplier];
}
if (self.secondAttribute == NSLayoutAttributeNotAnAttribute) {
[description appendFormat:@" %g", self.constant];
} else {
if (self.constant) {
[description appendFormat:@" %@ %g", (self.constant < 0 ? @"-" : @"+"), ABS(self.constant)];
}
}
if (self.priority != MASLayoutPriorityRequired) {
[description appendFormat:@" ^%@", self.class.layoutPriorityDescriptionsByValue[@(self.priority)] ?: [NSNumber numberWithDouble:self.priority]];
}
[description appendString:@">"];
return description;
}
@end
================================================
FILE: Pods/Masonry/Masonry/View+MASAdditions.h
================================================
//
// UIView+MASAdditions.h
// Masonry
//
// Created by Jonas Budelmann on 20/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASUtilities.h"
#import "MASConstraintMaker.h"
#import "MASViewAttribute.h"
/**
* Provides constraint maker block
* and convience methods for creating MASViewAttribute which are view + NSLayoutAttribute pairs
*/
@interface MAS_VIEW (MASAdditions)
/**
* following properties return a new MASViewAttribute with current view and appropriate NSLayoutAttribute
*/
@property (nonatomic, strong, readonly) MASViewAttribute *mas_left;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_top;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_right;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_bottom;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_leading;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_trailing;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_width;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_height;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerX;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerY;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_baseline;
@property (nonatomic, strong, readonly) MASViewAttribute *(^mas_attribute)(NSLayoutAttribute attr);
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
@property (nonatomic, strong, readonly) MASViewAttribute *mas_firstBaseline;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_lastBaseline;
#endif
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)
@property (nonatomic, strong, readonly) MASViewAttribute *mas_leftMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_rightMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_topMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_bottomMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_leadingMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_trailingMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerXWithinMargins;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerYWithinMargins;
#endif
#if (__IPHONE_OS_VERSION_MAX_ALLOWED >= 110000) || (__TV_OS_VERSION_MAX_ALLOWED >= 110000)
@property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuide API_AVAILABLE(ios(11.0),tvos(11.0));
@property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuideTop API_AVAILABLE(ios(11.0),tvos(11.0));
@property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuideBottom API_AVAILABLE(ios(11.0),tvos(11.0));
@property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuideLeft API_AVAILABLE(ios(11.0),tvos(11.0));
@property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuideRight API_AVAILABLE(ios(11.0),tvos(11.0));
#endif
/**
* a key to associate with this view
*/
@property (nonatomic, strong) id mas_key;
/**
* Finds the closest common superview between this view and another view
*
* @param view other view
*
* @return returns nil if common superview could not be found
*/
- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view;
/**
* Creates a MASConstraintMaker with the callee view.
* Any constraints defined are added to the view or the appropriate superview once the block has finished executing
*
* @param block scope within which you can build up the constraints which you wish to apply to the view.
*
* @return Array of created MASConstraints
*/
- (NSArray *)mas_makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
/**
* Creates a MASConstraintMaker with the callee view.
* Any constraints defined are added to the view or the appropriate superview once the block has finished executing.
* If an existing constraint exists then it will be updated instead.
*
* @param block scope within which you can build up the constraints which you wish to apply to the view.
*
* @return Array of created/updated MASConstraints
*/
- (NSArray *)mas_updateConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
/**
* Creates a MASConstraintMaker with the callee view.
* Any constraints defined are added to the view or the appropriate superview once the block has finished executing.
* All constraints previously installed for the view will be removed.
*
* @param block scope within which you can build up the constraints which you wish to apply to the view.
*
* @return Array of created/updated MASConstraints
*/
- (NSArray *)mas_remakeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
@end
================================================
FILE: Pods/Masonry/Masonry/View+MASAdditions.m
================================================
//
// UIView+MASAdditions.m
// Masonry
//
// Created by Jonas Budelmann on 20/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "View+MASAdditions.h"
#import
@implementation MAS_VIEW (MASAdditions)
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
constraintMaker.updateExisting = YES;
block(constraintMaker);
return [constraintMaker install];
}
- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
constraintMaker.removeExisting = YES;
block(constraintMaker);
return [constraintMaker install];
}
#pragma mark - NSLayoutAttribute properties
- (MASViewAttribute *)mas_left {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeLeft];
}
- (MASViewAttribute *)mas_top {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeTop];
}
- (MASViewAttribute *)mas_right {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeRight];
}
- (MASViewAttribute *)mas_bottom {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeBottom];
}
- (MASViewAttribute *)mas_leading {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeLeading];
}
- (MASViewAttribute *)mas_trailing {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeTrailing];
}
- (MASViewAttribute *)mas_width {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeWidth];
}
- (MASViewAttribute *)mas_height {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeHeight];
}
- (MASViewAttribute *)mas_centerX {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeCenterX];
}
- (MASViewAttribute *)mas_centerY {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeCenterY];
}
- (MASViewAttribute *)mas_baseline {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeBaseline];
}
- (MASViewAttribute *(^)(NSLayoutAttribute))mas_attribute
{
return ^(NSLayoutAttribute attr) {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:attr];
};
}
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
- (MASViewAttribute *)mas_firstBaseline {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeFirstBaseline];
}
- (MASViewAttribute *)mas_lastBaseline {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeLastBaseline];
}
#endif
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)
- (MASViewAttribute *)mas_leftMargin {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeLeftMargin];
}
- (MASViewAttribute *)mas_rightMargin {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeRightMargin];
}
- (MASViewAttribute *)mas_topMargin {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeTopMargin];
}
- (MASViewAttribute *)mas_bottomMargin {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeBottomMargin];
}
- (MASViewAttribute *)mas_leadingMargin {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeLeadingMargin];
}
- (MASViewAttribute *)mas_trailingMargin {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeTrailingMargin];
}
- (MASViewAttribute *)mas_centerXWithinMargins {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeCenterXWithinMargins];
}
- (MASViewAttribute *)mas_centerYWithinMargins {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeCenterYWithinMargins];
}
#endif
#if (__IPHONE_OS_VERSION_MAX_ALLOWED >= 110000) || (__TV_OS_VERSION_MAX_ALLOWED >= 110000)
- (MASViewAttribute *)mas_safeAreaLayoutGuide {
return [[MASViewAttribute alloc] initWithView:self item:self.safeAreaLayoutGuide layoutAttribute:NSLayoutAttributeBottom];
}
- (MASViewAttribute *)mas_safeAreaLayoutGuideTop {
return [[MASViewAttribute alloc] initWithView:self item:self.safeAreaLayoutGuide layoutAttribute:NSLayoutAttributeTop];
}
- (MASViewAttribute *)mas_safeAreaLayoutGuideBottom {
return [[MASViewAttribute alloc] initWithView:self item:self.safeAreaLayoutGuide layoutAttribute:NSLayoutAttributeBottom];
}
- (MASViewAttribute *)mas_safeAreaLayoutGuideLeft {
return [[MASViewAttribute alloc] initWithView:self item:self.safeAreaLayoutGuide layoutAttribute:NSLayoutAttributeLeft];
}
- (MASViewAttribute *)mas_safeAreaLayoutGuideRight {
return [[MASViewAttribute alloc] initWithView:self item:self.safeAreaLayoutGuide layoutAttribute:NSLayoutAttributeRight];
}
#endif
#pragma mark - associated properties
- (id)mas_key {
return objc_getAssociatedObject(self, @selector(mas_key));
}
- (void)setMas_key:(id)key {
objc_setAssociatedObject(self, @selector(mas_key), key, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#pragma mark - heirachy
- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view {
MAS_VIEW *closestCommonSuperview = nil;
MAS_VIEW *secondViewSuperview = view;
while (!closestCommonSuperview && secondViewSuperview) {
MAS_VIEW *firstViewSuperview = self;
while (!closestCommonSuperview && firstViewSuperview) {
if (secondViewSuperview == firstViewSuperview) {
closestCommonSuperview = secondViewSuperview;
}
firstViewSuperview = firstViewSuperview.superview;
}
secondViewSuperview = secondViewSuperview.superview;
}
return closestCommonSuperview;
}
@end
================================================
FILE: Pods/Masonry/Masonry/View+MASShorthandAdditions.h
================================================
//
// UIView+MASShorthandAdditions.h
// Masonry
//
// Created by Jonas Budelmann on 22/07/13.
// Copyright (c) 2013 Jonas Budelmann. All rights reserved.
//
#import "View+MASAdditions.h"
#ifdef MAS_SHORTHAND
/**
* Shorthand view additions without the 'mas_' prefixes,
* only enabled if MAS_SHORTHAND is defined
*/
@interface MAS_VIEW (MASShorthandAdditions)
@property (nonatomic, strong, readonly) MASViewAttribute *left;
@property (nonatomic, strong, readonly) MASViewAttribute *top;
@property (nonatomic, strong, readonly) MASViewAttribute *right;
@property (nonatomic, strong, readonly) MASViewAttribute *bottom;
@property (nonatomic, strong, readonly) MASViewAttribute *leading;
@property (nonatomic, strong, readonly) MASViewAttribute *trailing;
@property (nonatomic, strong, readonly) MASViewAttribute *width;
@property (nonatomic, strong, readonly) MASViewAttribute *height;
@property (nonatomic, strong, readonly) MASViewAttribute *centerX;
@property (nonatomic, strong, readonly) MASViewAttribute *centerY;
@property (nonatomic, strong, readonly) MASViewAttribute *baseline;
@property (nonatomic, strong, readonly) MASViewAttribute *(^attribute)(NSLayoutAttribute attr);
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
@property (nonatomic, strong, readonly) MASViewAttribute *firstBaseline;
@property (nonatomic, strong, readonly) MASViewAttribute *lastBaseline;
#endif
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)
@property (nonatomic, strong, readonly) MASViewAttribute *leftMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *rightMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *topMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *bottomMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *leadingMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *trailingMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *centerXWithinMargins;
@property (nonatomic, strong, readonly) MASViewAttribute *centerYWithinMargins;
#endif
#if (__IPHONE_OS_VERSION_MAX_ALLOWED >= 110000) || (__TV_OS_VERSION_MAX_ALLOWED >= 110000)
@property (nonatomic, strong, readonly) MASViewAttribute *safeAreaLayoutGuideTop API_AVAILABLE(ios(11.0),tvos(11.0));
@property (nonatomic, strong, readonly) MASViewAttribute *safeAreaLayoutGuideBottom API_AVAILABLE(ios(11.0),tvos(11.0));
@property (nonatomic, strong, readonly) MASViewAttribute *safeAreaLayoutGuideLeft API_AVAILABLE(ios(11.0),tvos(11.0));
@property (nonatomic, strong, readonly) MASViewAttribute *safeAreaLayoutGuideRight API_AVAILABLE(ios(11.0),tvos(11.0));
#endif
- (NSArray *)makeConstraints:(void(^)(MASConstraintMaker *make))block;
- (NSArray *)updateConstraints:(void(^)(MASConstraintMaker *make))block;
- (NSArray *)remakeConstraints:(void(^)(MASConstraintMaker *make))block;
@end
#define MAS_ATTR_FORWARD(attr) \
- (MASViewAttribute *)attr { \
return [self mas_##attr]; \
}
@implementation MAS_VIEW (MASShorthandAdditions)
MAS_ATTR_FORWARD(top);
MAS_ATTR_FORWARD(left);
MAS_ATTR_FORWARD(bottom);
MAS_ATTR_FORWARD(right);
MAS_ATTR_FORWARD(leading);
MAS_ATTR_FORWARD(trailing);
MAS_ATTR_FORWARD(width);
MAS_ATTR_FORWARD(height);
MAS_ATTR_FORWARD(centerX);
MAS_ATTR_FORWARD(centerY);
MAS_ATTR_FORWARD(baseline);
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
MAS_ATTR_FORWARD(firstBaseline);
MAS_ATTR_FORWARD(lastBaseline);
#endif
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)
MAS_ATTR_FORWARD(leftMargin);
MAS_ATTR_FORWARD(rightMargin);
MAS_ATTR_FORWARD(topMargin);
MAS_ATTR_FORWARD(bottomMargin);
MAS_ATTR_FORWARD(leadingMargin);
MAS_ATTR_FORWARD(trailingMargin);
MAS_ATTR_FORWARD(centerXWithinMargins);
MAS_ATTR_FORWARD(centerYWithinMargins);
#endif
#if (__IPHONE_OS_VERSION_MAX_ALLOWED >= 110000) || (__TV_OS_VERSION_MAX_ALLOWED >= 110000)
MAS_ATTR_FORWARD(safeAreaLayoutGuideTop);
MAS_ATTR_FORWARD(safeAreaLayoutGuideBottom);
MAS_ATTR_FORWARD(safeAreaLayoutGuideLeft);
MAS_ATTR_FORWARD(safeAreaLayoutGuideRight);
#endif
- (MASViewAttribute *(^)(NSLayoutAttribute))attribute {
return [self mas_attribute];
}
- (NSArray *)makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *))block {
return [self mas_makeConstraints:block];
}
- (NSArray *)updateConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *))block {
return [self mas_updateConstraints:block];
}
- (NSArray *)remakeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *))block {
return [self mas_remakeConstraints:block];
}
@end
#endif
================================================
FILE: Pods/Masonry/Masonry/ViewController+MASAdditions.h
================================================
//
// UIViewController+MASAdditions.h
// Masonry
//
// Created by Craig Siemens on 2015-06-23.
//
//
#import "MASUtilities.h"
#import "MASConstraintMaker.h"
#import "MASViewAttribute.h"
#ifdef MAS_VIEW_CONTROLLER
@interface MAS_VIEW_CONTROLLER (MASAdditions)
/**
* following properties return a new MASViewAttribute with appropriate UILayoutGuide and NSLayoutAttribute
*/
@property (nonatomic, strong, readonly) MASViewAttribute *mas_topLayoutGuide;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_bottomLayoutGuide;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_topLayoutGuideTop;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_topLayoutGuideBottom;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_bottomLayoutGuideTop;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_bottomLayoutGuideBottom;
@end
#endif
================================================
FILE: Pods/Masonry/Masonry/ViewController+MASAdditions.m
================================================
//
// UIViewController+MASAdditions.m
// Masonry
//
// Created by Craig Siemens on 2015-06-23.
//
//
#import "ViewController+MASAdditions.h"
#ifdef MAS_VIEW_CONTROLLER
@implementation MAS_VIEW_CONTROLLER (MASAdditions)
- (MASViewAttribute *)mas_topLayoutGuide {
return [[MASViewAttribute alloc] initWithView:self.view item:self.topLayoutGuide layoutAttribute:NSLayoutAttributeBottom];
}
- (MASViewAttribute *)mas_topLayoutGuideTop {
return [[MASViewAttribute alloc] initWithView:self.view item:self.topLayoutGuide layoutAttribute:NSLayoutAttributeTop];
}
- (MASViewAttribute *)mas_topLayoutGuideBottom {
return [[MASViewAttribute alloc] initWithView:self.view item:self.topLayoutGuide layoutAttribute:NSLayoutAttributeBottom];
}
- (MASViewAttribute *)mas_bottomLayoutGuide {
return [[MASViewAttribute alloc] initWithView:self.view item:self.bottomLayoutGuide layoutAttribute:NSLayoutAttributeTop];
}
- (MASViewAttribute *)mas_bottomLayoutGuideTop {
return [[MASViewAttribute alloc] initWithView:self.view item:self.bottomLayoutGuide layoutAttribute:NSLayoutAttributeTop];
}
- (MASViewAttribute *)mas_bottomLayoutGuideBottom {
return [[MASViewAttribute alloc] initWithView:self.view item:self.bottomLayoutGuide layoutAttribute:NSLayoutAttributeBottom];
}
@end
#endif
================================================
FILE: Pods/Masonry/README.md
================================================
# Masonry [](https://travis-ci.org/SnapKit/Masonry) [](https://coveralls.io/r/SnapKit/Masonry) [](https://github.com/Carthage/Carthage) 
**Masonry is still actively maintained, we are committed to fixing bugs and merging good quality PRs from the wider community. However if you're using Swift in your project, we recommend using [SnapKit](https://github.com/SnapKit/SnapKit) as it provides better type safety with a simpler API.**
Masonry is a light-weight layout framework which wraps AutoLayout with a nicer syntax. Masonry has its own layout DSL which provides a chainable way of describing your NSLayoutConstraints which results in layout code that is more concise and readable.
Masonry supports iOS and Mac OS X.
For examples take a look at the **Masonry iOS Examples** project in the Masonry workspace. You will need to run `pod install` after downloading.
## What's wrong with NSLayoutConstraints?
Under the hood Auto Layout is a powerful and flexible way of organising and laying out your views. However creating constraints from code is verbose and not very descriptive.
Imagine a simple example in which you want to have a view fill its superview but inset by 10 pixels on every side
```obj-c
UIView *superview = self.view;
UIView *view1 = [[UIView alloc] init];
view1.translatesAutoresizingMaskIntoConstraints = NO;
view1.backgroundColor = [UIColor greenColor];
[superview addSubview:view1];
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[superview addConstraints:@[
//view1 constraints
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:padding.top],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:padding.left],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-padding.bottom],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeRight
multiplier:1
constant:-padding.right],
]];
```
Even with such a simple example the code needed is quite verbose and quickly becomes unreadable when you have more than 2 or 3 views.
Another option is to use Visual Format Language (VFL), which is a bit less long winded.
However the ASCII type syntax has its own pitfalls and its also a bit harder to animate as `NSLayoutConstraint constraintsWithVisualFormat:` returns an array.
## Prepare to meet your Maker!
Heres the same constraints created using MASConstraintMaker
```obj-c
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler
make.left.equalTo(superview.mas_left).with.offset(padding.left);
make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}];
```
Or even shorter
```obj-c
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(superview).with.insets(padding);
}];
```
Also note in the first example we had to add the constraints to the superview `[superview addConstraints:...`.
Masonry however will automagically add constraints to the appropriate view.
Masonry will also call `view1.translatesAutoresizingMaskIntoConstraints = NO;` for you.
## Not all things are created equal
> `.equalTo` equivalent to **NSLayoutRelationEqual**
> `.lessThanOrEqualTo` equivalent to **NSLayoutRelationLessThanOrEqual**
> `.greaterThanOrEqualTo` equivalent to **NSLayoutRelationGreaterThanOrEqual**
These three equality constraints accept one argument which can be any of the following:
#### 1. MASViewAttribute
```obj-c
make.centerX.lessThanOrEqualTo(view2.mas_left);
```
MASViewAttribute | NSLayoutAttribute
------------------------- | --------------------------
view.mas_left | NSLayoutAttributeLeft
view.mas_right | NSLayoutAttributeRight
view.mas_top | NSLayoutAttributeTop
view.mas_bottom | NSLayoutAttributeBottom
view.mas_leading | NSLayoutAttributeLeading
view.mas_trailing | NSLayoutAttributeTrailing
view.mas_width | NSLayoutAttributeWidth
view.mas_height | NSLayoutAttributeHeight
view.mas_centerX | NSLayoutAttributeCenterX
view.mas_centerY | NSLayoutAttributeCenterY
view.mas_baseline | NSLayoutAttributeBaseline
#### 2. UIView/NSView
if you want view.left to be greater than or equal to label.left :
```obj-c
//these two constraints are exactly the same
make.left.greaterThanOrEqualTo(label);
make.left.greaterThanOrEqualTo(label.mas_left);
```
#### 3. NSNumber
Auto Layout allows width and height to be set to constant values.
if you want to set view to have a minimum and maximum width you could pass a number to the equality blocks:
```obj-c
//width >= 200 && width <= 400
make.width.greaterThanOrEqualTo(@200);
make.width.lessThanOrEqualTo(@400)
```
However Auto Layout does not allow alignment attributes such as left, right, centerY etc to be set to constant values.
So if you pass a NSNumber for these attributes Masonry will turn these into constraints relative to the view’s superview ie:
```obj-c
//creates view.left = view.superview.left + 10
make.left.lessThanOrEqualTo(@10)
```
Instead of using NSNumber, you can use primitives and structs to build your constraints, like so:
```obj-c
make.top.mas_equalTo(42);
make.height.mas_equalTo(20);
make.size.mas_equalTo(CGSizeMake(50, 100));
make.edges.mas_equalTo(UIEdgeInsetsMake(10, 0, 10, 0));
make.left.mas_equalTo(view).mas_offset(UIEdgeInsetsMake(10, 0, 10, 0));
```
By default, macros which support [autoboxing](https://en.wikipedia.org/wiki/Autoboxing#Autoboxing) are prefixed with `mas_`. Unprefixed versions are available by defining `MAS_SHORTHAND_GLOBALS` before importing Masonry.
#### 4. NSArray
An array of a mixture of any of the previous types
```obj-c
make.height.equalTo(@[view1.mas_height, view2.mas_height]);
make.height.equalTo(@[view1, view2]);
make.left.equalTo(@[view1, @100, view3.right]);
````
## Learn to prioritize
> `.priority` allows you to specify an exact priority
> `.priorityHigh` equivalent to **UILayoutPriorityDefaultHigh**
> `.priorityMedium` is half way between high and low
> `.priorityLow` equivalent to **UILayoutPriorityDefaultLow**
Priorities are can be tacked on to the end of a constraint chain like so:
```obj-c
make.left.greaterThanOrEqualTo(label.mas_left).with.priorityLow();
make.top.equalTo(label.mas_top).with.priority(600);
```
## Composition, composition, composition
Masonry also gives you a few convenience methods which create multiple constraints at the same time. These are called MASCompositeConstraints
#### edges
```obj-c
// make top, left, bottom, right equal view2
make.edges.equalTo(view2);
// make top = superview.top + 5, left = superview.left + 10,
// bottom = superview.bottom - 15, right = superview.right - 20
make.edges.equalTo(superview).insets(UIEdgeInsetsMake(5, 10, 15, 20))
```
#### size
```obj-c
// make width and height greater than or equal to titleLabel
make.size.greaterThanOrEqualTo(titleLabel)
// make width = superview.width + 100, height = superview.height - 50
make.size.equalTo(superview).sizeOffset(CGSizeMake(100, -50))
```
#### center
```obj-c
// make centerX and centerY = button1
make.center.equalTo(button1)
// make centerX = superview.centerX - 5, centerY = superview.centerY + 10
make.center.equalTo(superview).centerOffset(CGPointMake(-5, 10))
```
You can chain view attributes for increased readability:
```obj-c
// All edges but the top should equal those of the superview
make.left.right.and.bottom.equalTo(superview);
make.top.equalTo(otherView);
```
## Hold on for dear life
Sometimes you need modify existing constraints in order to animate or remove/replace constraints.
In Masonry there are a few different approaches to updating constraints.
#### 1. References
You can hold on to a reference of a particular constraint by assigning the result of a constraint make expression to a local variable or a class property.
You could also reference multiple constraints by storing them away in an array.
```obj-c
// in public/private interface
@property (nonatomic, strong) MASConstraint *topConstraint;
...
// when making constraints
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
self.topConstraint = make.top.equalTo(superview.mas_top).with.offset(padding.top);
make.left.equalTo(superview.mas_left).with.offset(padding.left);
}];
...
// then later you can call
[self.topConstraint uninstall];
```
#### 2. mas_updateConstraints
Alternatively if you are only updating the constant value of the constraint you can use the convience method `mas_updateConstraints` instead of `mas_makeConstraints`
```obj-c
// this is Apple's recommended place for adding/updating constraints
// this method can get called multiple times in response to setNeedsUpdateConstraints
// which can be called by UIKit internally or in your code if you need to trigger an update to your constraints
- (void)updateConstraints {
[self.growingButton mas_updateConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self);
make.width.equalTo(@(self.buttonSize.width)).priorityLow();
make.height.equalTo(@(self.buttonSize.height)).priorityLow();
make.width.lessThanOrEqualTo(self);
make.height.lessThanOrEqualTo(self);
}];
//according to apple super should be called at end of method
[super updateConstraints];
}
```
### 3. mas_remakeConstraints
`mas_updateConstraints` is useful for updating a set of constraints, but doing anything beyond updating constant values can get exhausting. That's where `mas_remakeConstraints` comes in.
`mas_remakeConstraints` is similar to `mas_updateConstraints`, but instead of updating constant values, it will remove all of its constraints before installing them again. This lets you provide different constraints without having to keep around references to ones which you want to remove.
```obj-c
- (void)changeButtonPosition {
[self.button mas_remakeConstraints:^(MASConstraintMaker *make) {
make.size.equalTo(self.buttonSize);
if (topLeft) {
make.top.and.left.offset(10);
} else {
make.bottom.and.right.offset(-10);
}
}];
}
```
You can find more detailed examples of all three approaches in the **Masonry iOS Examples** project.
## When the ^&*!@ hits the fan!
Laying out your views doesn't always goto plan. So when things literally go pear shaped, you don't want to be looking at console output like this:
```obj-c
Unable to simultaneously satisfy constraints.....blah blah blah....
(
"=5000)]>",
"",
"",
""
)
Will attempt to recover by breaking constraint
=5000)]>
```
Masonry adds a category to NSLayoutConstraint which overrides the default implementation of `- (NSString *)description`.
Now you can give meaningful names to views and constraints, and also easily pick out the constraints created by Masonry.
which means your console output can now look like this:
```obj-c
Unable to simultaneously satisfy constraints......blah blah blah....
(
"",
"= 5000>",
"",
""
)
Will attempt to recover by breaking constraint
= 5000>
```
For an example of how to set this up take a look at the **Masonry iOS Examples** project in the Masonry workspace.
## Where should I create my constraints?
```objc
@implementation DIYCustomView
- (id)init {
self = [super init];
if (!self) return nil;
// --- Create your views here ---
self.button = [[UIButton alloc] init];
return self;
}
// tell UIKit that you are using AutoLayout
+ (BOOL)requiresConstraintBasedLayout {
return YES;
}
// this is Apple's recommended place for adding/updating constraints
- (void)updateConstraints {
// --- remake/update constraints here
[self.button remakeConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(@(self.buttonSize.width));
make.height.equalTo(@(self.buttonSize.height));
}];
//according to apple super should be called at end of method
[super updateConstraints];
}
- (void)didTapButton:(UIButton *)button {
// --- Do your changes ie change variables that affect your layout etc ---
self.buttonSize = CGSize(200, 200);
// tell constraints they need updating
[self setNeedsUpdateConstraints];
}
@end
```
## Installation
Use the [orsome](http://www.youtube.com/watch?v=YaIZF8uUTtk) [CocoaPods](http://github.com/CocoaPods/CocoaPods).
In your Podfile
>`pod 'Masonry'`
If you want to use masonry without all those pesky 'mas_' prefixes. Add #define MAS_SHORTHAND to your prefix.pch before importing Masonry
>`#define MAS_SHORTHAND`
Get busy Masoning
>`#import "Masonry.h"`
## Code Snippets
Copy the included code snippets to ``~/Library/Developer/Xcode/UserData/CodeSnippets`` to write your masonry blocks at lightning speed!
`mas_make` -> ` [<#view#> mas_makeConstraints:^(MASConstraintMaker *make) {
<#code#>
}];`
`mas_update` -> ` [<#view#> mas_updateConstraints:^(MASConstraintMaker *make) {
<#code#>
}];`
`mas_remake` -> ` [<#view#> mas_remakeConstraints:^(MASConstraintMaker *make) {
<#code#>
}];`
## Features
* Not limited to subset of Auto Layout. Anything NSLayoutConstraint can do, Masonry can do too!
* Great debug support, give your views and constraints meaningful names.
* Constraints read like sentences.
* No crazy macro magic. Masonry won't pollute the global namespace with macros.
* Not string or dictionary based and hence you get compile time checking.
## TODO
* Eye candy
* Mac example project
* More tests and examples
================================================
FILE: Pods/OCMock/License.txt
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
================================================
FILE: Pods/OCMock/README.md
================================================
OCMock
======

OCMock is an Objective-C implementation of mock objects.
For downloads, documentation, and support please visit [ocmock.org](http://ocmock.org/).
================================================
FILE: Pods/OCMock/Source/OCMock/NSInvocation+OCMAdditions.h
================================================
/*
* Copyright (c) 2006-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
@interface NSInvocation(OCMAdditions)
+ (NSInvocation *)invocationForBlock:(id)block withArguments:(NSArray *)arguments;
- (void)retainObjectArgumentsExcludingObject:(id)objectToExclude;
- (id)getArgumentAtIndexAsObject:(NSInteger)argIndex;
- (NSString *)invocationDescription;
- (NSString *)argumentDescriptionAtIndex:(NSInteger)argIndex;
- (NSString *)objectDescriptionAtIndex:(NSInteger)anInt;
- (NSString *)charDescriptionAtIndex:(NSInteger)anInt;
- (NSString *)unsignedCharDescriptionAtIndex:(NSInteger)anInt;
- (NSString *)intDescriptionAtIndex:(NSInteger)anInt;
- (NSString *)unsignedIntDescriptionAtIndex:(NSInteger)anInt;
- (NSString *)shortDescriptionAtIndex:(NSInteger)anInt;
- (NSString *)unsignedShortDescriptionAtIndex:(NSInteger)anInt;
- (NSString *)longDescriptionAtIndex:(NSInteger)anInt;
- (NSString *)unsignedLongDescriptionAtIndex:(NSInteger)anInt;
- (NSString *)longLongDescriptionAtIndex:(NSInteger)anInt;
- (NSString *)unsignedLongLongDescriptionAtIndex:(NSInteger)anInt;
- (NSString *)doubleDescriptionAtIndex:(NSInteger)anInt;
- (NSString *)floatDescriptionAtIndex:(NSInteger)anInt;
- (NSString *)structDescriptionAtIndex:(NSInteger)anInt;
- (NSString *)pointerDescriptionAtIndex:(NSInteger)anInt;
- (NSString *)cStringDescriptionAtIndex:(NSInteger)anInt;
- (NSString *)selectorDescriptionAtIndex:(NSInteger)anInt;
- (BOOL)methodIsInInitFamily;
- (BOOL)methodIsInCreateFamily;
@end
================================================
FILE: Pods/OCMock/Source/OCMock/NSInvocation+OCMAdditions.m
================================================
/*
* Copyright (c) 2006-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
#import "NSInvocation+OCMAdditions.h"
#import "NSMethodSignature+OCMAdditions.h"
#import "OCMArg.h"
#import "OCMFunctionsPrivate.h"
#if(TARGET_OS_OSX && (!defined(__MAC_10_10) || __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_10)) || \
(TARGET_OS_IPHONE && (!defined(__IPHONE_8_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0))
static BOOL OCMObjectIsClass(id object)
{
return class_isMetaClass(object_getClass(object));
}
#define object_isClass OCMObjectIsClass
#endif
static NSString *const OCMArgAnyPointerDescription = @"<[OCMArg anyPointer]>";
@implementation NSInvocation(OCMAdditions)
+ (NSInvocation *)invocationForBlock:(id)block withArguments:(NSArray *)arguments
{
NSMethodSignature *sig = [NSMethodSignature signatureForBlock:block];
NSInvocation *inv = [self invocationWithMethodSignature:sig];
NSUInteger numArgsRequired = sig.numberOfArguments - 1;
if((arguments != nil) && ([arguments count] != numArgsRequired))
[NSException raise:NSInvalidArgumentException format:@"Specified too few arguments for block; expected %lu arguments.", (unsigned long)numArgsRequired];
for(NSUInteger i = 0, j = 1; i < numArgsRequired; ++i, ++j)
{
id arg = [arguments objectAtIndex:i];
[inv setArgumentWithObject:arg atIndex:j];
}
return inv;
}
static NSString *const OCMRetainedObjectArgumentsKey = @"OCMRetainedObjectArgumentsKey";
- (void)retainObjectArgumentsExcludingObject:(id)objectToExclude
{
if(objc_getAssociatedObject(self, OCMRetainedObjectArgumentsKey) != nil)
{
// looks like we've retained the arguments already; do nothing else
return;
}
NSMutableArray *retainedArguments = [[NSMutableArray alloc] init];
id target = [self target];
if((target != nil) && (target != objectToExclude) && !object_isClass(target))
{
// Bad things will happen if the target is a block since it's not being
// copied. There isn't a very good way to tell if an invocation's target
// is a block though (the argument type at index 0 is always "@" even if
// the target is a Class or block), and in practice it's OK since you
// can't mock a block.
[retainedArguments addObject:target];
}
NSUInteger numberOfArguments = [[self methodSignature] numberOfArguments];
for(NSUInteger index = 2; index < numberOfArguments; index++)
{
const char *argumentType = [[self methodSignature] getArgumentTypeAtIndex:index];
if(OCMIsObjectType(argumentType))
{
id argument;
[self getArgument:&argument atIndex:index];
if((argument != nil) && (argument != objectToExclude))
{
if(OCMIsBlockType(argumentType) && OCMIsBlock(argument))
{
// The argument's type is block and the passed argument is a block. In this
// case we can't retain the argument because it might be stack block, which
// must be copied. Further, non-escaping blocks have a lifetime that is stack-
// based and they treat copy/release as a no-op. Keeping a reference to these
// would result in a dangling pointer, which is why they are ignored here.
// Note: even when the argument's type is block the argument could be
// something else, e.g. an instance of OCMConstraint. Such cases are handled
// like regular objects in the last else branch below.
if(OCMIsNonEscapingBlock(argument) == NO)
{
id blockArgument = [argument copy];
[retainedArguments addObject:blockArgument];
[blockArgument release];
}
}
else if(OCMIsClassType(argumentType) && object_isClass(argument))
{
// The argument's type is class and the passed argument is a class. In this
// case do not retain the argument. Note: Even though the type is class the
// argument could be a non-class, e.g. an instance of OCMArg.
}
else
{
[retainedArguments addObject:argument];
}
}
}
}
objc_setAssociatedObject(self, OCMRetainedObjectArgumentsKey, retainedArguments, OBJC_ASSOCIATION_RETAIN);
[retainedArguments release];
}
- (void)setArgumentWithObject:(id)arg atIndex:(NSInteger)idx
{
const char *typeEncoding = [[self methodSignature] getArgumentTypeAtIndex:idx];
if((arg == nil) || ([arg respondsToSelector:@selector(isKindOfClass:)] && [arg isKindOfClass:[NSNull class]]))
{
if(OCMIsPointerType(typeEncoding))
{
void *nullPtr = NULL;
[self setArgument:&nullPtr atIndex:idx];
}
else if(typeEncoding[0] == '@')
{
id nilObj = nil;
[self setArgument:&nilObj atIndex:idx];
}
else if(OCMNumberTypeForObjCType(typeEncoding))
{
NSUInteger argSize;
NSGetSizeAndAlignment(typeEncoding, &argSize, NULL);
void *argBuffer = calloc(1, argSize);
[self setArgument:argBuffer atIndex:idx];
free(argBuffer);
}
else
{
[NSException raise:NSInvalidArgumentException format:@"Unable to create default value for type '%s'.", typeEncoding];
}
}
else if(OCMIsObjectType(typeEncoding))
{
[self setArgument:&arg atIndex:idx];
}
else
{
if(![arg isKindOfClass:[NSValue class]])
[NSException raise:NSInvalidArgumentException format:@"Argument '%@' should be boxed in NSValue.", arg];
char const *valEncoding = [arg objCType];
/// @note Here we allow any data pointer to be passed as a void pointer and
/// any numerical types to be passed as arguments to the block.
BOOL takesVoidPtr = !strcmp(typeEncoding, "^v") && valEncoding[0] == '^';
BOOL takesNumber = OCMNumberTypeForObjCType(typeEncoding) && OCMNumberTypeForObjCType(valEncoding);
if(!takesVoidPtr && !takesNumber && !OCMEqualTypesAllowingOpaqueStructs(typeEncoding, valEncoding))
[NSException raise:NSInvalidArgumentException
format:@"Argument type mismatch; type of argument required is '%s' but type of value provided is '%s'",
typeEncoding, valEncoding];
NSUInteger argSize;
NSGetSizeAndAlignment(typeEncoding, &argSize, NULL);
void *argBuffer = malloc(argSize);
[arg getValue:argBuffer];
[self setArgument:argBuffer atIndex:idx];
free(argBuffer);
}
}
- (id)getArgumentAtIndexAsObject:(NSInteger)argIndex
{
const char *argType = OCMTypeWithoutQualifiers([[self methodSignature] getArgumentTypeAtIndex:(NSUInteger)argIndex]);
if((strlen(argType) > 1) && (strchr("{^", argType[0]) == NULL) && (strcmp("@?", argType) != 0))
[NSException raise:NSInvalidArgumentException format:@"Cannot handle argument type '%s'.", argType];
if(OCMIsObjectType(argType))
{
id value;
[self getArgument:&value atIndex:argIndex];
return value;
}
switch(argType[0])
{
case ':':
{
SEL s = (SEL)0;
[self getArgument:&s atIndex:argIndex];
return [NSValue valueWithBytes:&s objCType:":"];
}
case 'i':
{
int value;
[self getArgument:&value atIndex:argIndex];
return @(value);
}
case 's':
{
short value;
[self getArgument:&value atIndex:argIndex];
return @(value);
}
case 'l':
{
long value;
[self getArgument:&value atIndex:argIndex];
return @(value);
}
case 'q':
{
long long value;
[self getArgument:&value atIndex:argIndex];
return @(value);
}
case 'c':
{
char value;
[self getArgument:&value atIndex:argIndex];
return @(value);
}
case 'C':
{
unsigned char value;
[self getArgument:&value atIndex:argIndex];
return @(value);
}
case 'I':
{
unsigned int value;
[self getArgument:&value atIndex:argIndex];
return @(value);
}
case 'S':
{
unsigned short value;
[self getArgument:&value atIndex:argIndex];
return @(value);
}
case 'L':
{
unsigned long value;
[self getArgument:&value atIndex:argIndex];
return @(value);
}
case 'Q':
{
unsigned long long value;
[self getArgument:&value atIndex:argIndex];
return @(value);
}
case 'f':
{
float value;
[self getArgument:&value atIndex:argIndex];
return @(value);
}
case 'd':
{
double value;
[self getArgument:&value atIndex:argIndex];
return @(value);
}
case 'D':
{
long double value;
[self getArgument:&value atIndex:argIndex];
return [NSValue valueWithBytes:&value objCType:@encode(__typeof__(value))];
}
case 'B':
{
bool value;
[self getArgument:&value atIndex:argIndex];
return @(value);
}
case '^':
case '*':
{
void *value = NULL;
[self getArgument:&value atIndex:argIndex];
return [NSValue valueWithPointer:value];
}
case '{': // structure
{
NSUInteger argSize;
NSGetSizeAndAlignment([[self methodSignature] getArgumentTypeAtIndex:(NSUInteger)argIndex], &argSize, NULL);
if(argSize == 0) // TODO: Can this happen? Is frameLength a good choice in that case?
argSize = [[self methodSignature] frameLength];
NSMutableData *argumentData = [[[NSMutableData alloc] initWithLength:argSize] autorelease];
[self getArgument:[argumentData mutableBytes] atIndex:argIndex];
return [NSValue valueWithBytes:[argumentData bytes] objCType:argType];
}
}
[NSException raise:NSInvalidArgumentException format:@"Argument type '%s' not supported", argType];
return nil;
}
- (NSString *)invocationDescription
{
NSMethodSignature *methodSignature = [self methodSignature];
NSUInteger numberOfArgs = [methodSignature numberOfArguments];
if(numberOfArgs == 2)
return NSStringFromSelector([self selector]);
NSArray *selectorParts = [NSStringFromSelector([self selector]) componentsSeparatedByString:@":"];
NSMutableString *description = [[NSMutableString alloc] init];
NSUInteger i;
for(i = 2; i < numberOfArgs; i++)
{
[description appendFormat:@"%@%@:", (i > 2 ? @" " : @""), [selectorParts objectAtIndex:(i - 2)]];
[description appendString:[self argumentDescriptionAtIndex:(NSInteger)i]];
}
return [description autorelease];
}
- (NSString *)argumentDescriptionAtIndex:(NSInteger)argIndex
{
const char *argType = OCMTypeWithoutQualifiers([[self methodSignature] getArgumentTypeAtIndex:(NSUInteger)argIndex]);
switch(*argType)
{
case '@': return [self objectDescriptionAtIndex:argIndex];
case 'B': return [self boolDescriptionAtIndex:argIndex];
case 'c': return [self charDescriptionAtIndex:argIndex];
case 'C': return [self unsignedCharDescriptionAtIndex:argIndex];
case 'i': return [self intDescriptionAtIndex:argIndex];
case 'I': return [self unsignedIntDescriptionAtIndex:argIndex];
case 's': return [self shortDescriptionAtIndex:argIndex];
case 'S': return [self unsignedShortDescriptionAtIndex:argIndex];
case 'l': return [self longDescriptionAtIndex:argIndex];
case 'L': return [self unsignedLongDescriptionAtIndex:argIndex];
case 'q': return [self longLongDescriptionAtIndex:argIndex];
case 'Q': return [self unsignedLongLongDescriptionAtIndex:argIndex];
case 'd': return [self doubleDescriptionAtIndex:argIndex];
case 'f': return [self floatDescriptionAtIndex:argIndex];
case 'D': return [self longDoubleDescriptionAtIndex:argIndex];
case '{': return [self structDescriptionAtIndex:argIndex];
case '^': return [self pointerDescriptionAtIndex:argIndex];
case '*': return [self cStringDescriptionAtIndex:argIndex];
case ':': return [self selectorDescriptionAtIndex:argIndex];
default: return [@"?" stringByAppendingString:@">"]; // avoid confusion with trigraphs...
}
}
- (NSString *)objectDescriptionAtIndex:(NSInteger)anInt
{
id object;
[self getArgument:&object atIndex:anInt];
if(object == nil)
return @"nil";
else if(![object isProxy] && [object isKindOfClass:[NSString class]])
return [NSString stringWithFormat:@"@\"%@\"", [object description]];
else
// The description cannot be nil, if it is then replace it
return [object description] ?: @"";
}
- (NSString *)boolDescriptionAtIndex:(NSInteger)anInt
{
bool value;
[self getArgument:&value atIndex:anInt];
return value ? @"YES" : @"NO";
}
- (NSString *)charDescriptionAtIndex:(NSInteger)anInt
{
unsigned char buffer[128];
memset(buffer, 0x0, 128);
[self getArgument:&buffer atIndex:anInt];
// If there's only one character in the buffer, and it's 0 or 1, then we have a BOOL
if(buffer[1] == '\0' && (buffer[0] == 0 || buffer[0] == 1))
return (buffer[0] == 1 ? @"YES" : @"NO");
else
return [NSString stringWithFormat:@"'%c'", *buffer];
}
- (NSString *)unsignedCharDescriptionAtIndex:(NSInteger)anInt
{
unsigned char buffer[128];
memset(buffer, 0x0, 128);
[self getArgument:&buffer atIndex:anInt];
return [NSString stringWithFormat:@"'%c'", *buffer];
}
- (NSString *)intDescriptionAtIndex:(NSInteger)anInt
{
int intValue;
[self getArgument:&intValue atIndex:anInt];
return [NSString stringWithFormat:@"%d", intValue];
}
- (NSString *)unsignedIntDescriptionAtIndex:(NSInteger)anInt
{
unsigned int intValue;
[self getArgument:&intValue atIndex:anInt];
return [NSString stringWithFormat:@"%d", intValue];
}
- (NSString *)shortDescriptionAtIndex:(NSInteger)anInt
{
short shortValue;
[self getArgument:&shortValue atIndex:anInt];
return [NSString stringWithFormat:@"%hi", shortValue];
}
- (NSString *)unsignedShortDescriptionAtIndex:(NSInteger)anInt
{
unsigned short shortValue;
[self getArgument:&shortValue atIndex:anInt];
return [NSString stringWithFormat:@"%hu", shortValue];
}
- (NSString *)longDescriptionAtIndex:(NSInteger)anInt
{
long longValue;
[self getArgument:&longValue atIndex:anInt];
return [NSString stringWithFormat:@"%ld", longValue];
}
- (NSString *)unsignedLongDescriptionAtIndex:(NSInteger)anInt
{
unsigned long longValue;
[self getArgument:&longValue atIndex:anInt];
return [NSString stringWithFormat:@"%lu", longValue];
}
- (NSString *)longLongDescriptionAtIndex:(NSInteger)anInt
{
long long longLongValue;
[self getArgument:&longLongValue atIndex:anInt];
return [NSString stringWithFormat:@"%qi", longLongValue];
}
- (NSString *)unsignedLongLongDescriptionAtIndex:(NSInteger)anInt
{
unsigned long long longLongValue;
[self getArgument:&longLongValue atIndex:anInt];
return [NSString stringWithFormat:@"%qu", longLongValue];
}
- (NSString *)doubleDescriptionAtIndex:(NSInteger)anInt
{
double doubleValue;
[self getArgument:&doubleValue atIndex:anInt];
return [NSString stringWithFormat:@"%f", doubleValue];
}
- (NSString *)floatDescriptionAtIndex:(NSInteger)anInt
{
float floatValue;
[self getArgument:&floatValue atIndex:anInt];
return [NSString stringWithFormat:@"%f", floatValue];
}
- (NSString *)longDoubleDescriptionAtIndex:(NSInteger)anInt
{
long double longDoubleValue;
[self getArgument:&longDoubleValue atIndex:anInt];
return [NSString stringWithFormat:@"%Lf", longDoubleValue];
}
- (NSString *)structDescriptionAtIndex:(NSInteger)anInt
{
return [NSString stringWithFormat:@"(%@)", [[self getArgumentAtIndexAsObject:anInt] description]];
}
- (NSString *)pointerDescriptionAtIndex:(NSInteger)anInt
{
void *buffer;
[self getArgument:&buffer atIndex:anInt];
if(buffer == [OCMArg anyPointer])
return OCMArgAnyPointerDescription;
else
return [NSString stringWithFormat:@"%p", buffer];
}
- (NSString *)cStringDescriptionAtIndex:(NSInteger)anInt
{
char *cStringPtr;
[self getArgument:&cStringPtr atIndex:anInt];
if(cStringPtr == [OCMArg anyPointer])
{
return OCMArgAnyPointerDescription;
}
else
{
char buffer[104];
strlcpy(buffer, cStringPtr, sizeof(buffer));
strlcpy(buffer + 100, "...", (sizeof(buffer) - 100));
return [NSString stringWithFormat:@"\"%s\"", buffer];
}
}
- (NSString *)selectorDescriptionAtIndex:(NSInteger)anInt
{
SEL selectorValue;
[self getArgument:&selectorValue atIndex:anInt];
return [NSString stringWithFormat:@"@selector(%@)", NSStringFromSelector(selectorValue)];
}
- (BOOL)isMethodFamily:(NSString *)family
{
// Definitions here: https://clang.llvm.org/docs/AutomaticReferenceCounting.html#method-families
NSMethodSignature *signature = [self methodSignature];
if(OCMIsObjectType(signature.methodReturnType) == NO)
{
return NO;
}
NSString *selString = NSStringFromSelector([self selector]);
NSRange underscoreRange = [selString rangeOfString:@"^_*" options:NSRegularExpressionSearch];
selString = [selString substringFromIndex:NSMaxRange(underscoreRange)];
if([selString hasPrefix:family] == NO)
{
return NO;
}
NSUInteger familyLength = [family length];
if(([selString length] > familyLength) &&
([[NSCharacterSet lowercaseLetterCharacterSet] characterIsMember:[selString characterAtIndex:familyLength]]))
{
return NO;
}
return YES;
}
- (BOOL)methodIsInInitFamily
{
return [self isMethodFamily:@"init"];
}
- (BOOL)methodIsInCreateFamily
{
return [self isMethodFamily:@"alloc"]
|| [self isMethodFamily:@"copy"]
|| [self isMethodFamily:@"mutableCopy"]
|| [self isMethodFamily:@"new"];
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/NSMethodSignature+OCMAdditions.h
================================================
/*
* Copyright (c) 2009-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
@interface NSMethodSignature(OCMAdditions)
+ (NSMethodSignature *)signatureForDynamicPropertyAccessedWithSelector:(SEL)selector inClass:(Class)aClass;
+ (NSMethodSignature *)signatureForBlock:(id)block;
- (BOOL)usesSpecialStructureReturn;
- (NSString *)fullTypeString;
- (const char *)fullObjCTypes;
@end
================================================
FILE: Pods/OCMock/Source/OCMock/NSMethodSignature+OCMAdditions.m
================================================
/*
* Copyright (c) 2009-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
#import "NSMethodSignature+OCMAdditions.h"
#import "OCMFunctionsPrivate.h"
@implementation NSMethodSignature(OCMAdditions)
#pragma mark Signatures for dynamic properties
+ (NSMethodSignature *)signatureForDynamicPropertyAccessedWithSelector:(SEL)selector inClass:(Class)aClass
{
BOOL isGetter = YES;
objc_property_t property = [self propertyMatchingSelector:selector inClass:aClass isGetter:&isGetter];
if(property == NULL)
return nil;
const char *propertyAttributesString = property_getAttributes(property);
NSArray *propertyAttributes = [[NSString stringWithCString:propertyAttributesString
encoding:NSASCIIStringEncoding] componentsSeparatedByString:@","];
NSString *typeStr = nil;
BOOL isDynamic = NO;
for(NSString *attribute in propertyAttributes)
{
if([attribute isEqualToString:@"D"])
isDynamic = YES;
else if([attribute hasPrefix:@"T"])
typeStr = [attribute substringFromIndex:1];
}
if(!isDynamic)
return nil;
NSRange r = [typeStr rangeOfString:@"\""]; // incomplete workaround to deal with structs
if(r.location != NSNotFound)
typeStr = [typeStr substringToIndex:r.location];
NSString *sigStringFormat = isGetter ? @"%@@:" : @"v@:%@";
const char *sigCString = [[NSString stringWithFormat:sigStringFormat, typeStr] cStringUsingEncoding:NSASCIIStringEncoding];
return [NSMethodSignature signatureWithObjCTypes:sigCString];
}
+ (objc_property_t)propertyMatchingSelector:(SEL)selector inClass:(Class)aClass isGetter:(BOOL *)isGetterPtr
{
NSString *propertyName = NSStringFromSelector(selector);
// first try selector as is aassuming it's a getter
objc_property_t property = class_getProperty(aClass, [propertyName cStringUsingEncoding:NSASCIIStringEncoding]);
if(property != NULL)
{
*isGetterPtr = YES;
return property;
}
// try setter next if selector starts with "set"
if([propertyName hasPrefix:@"set"])
{
propertyName = [propertyName substringFromIndex:@"set".length];
propertyName = [propertyName stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[[propertyName substringToIndex:1] lowercaseString]];
if([propertyName hasSuffix:@":"])
propertyName = [propertyName substringToIndex:[propertyName length] - 1];
property = class_getProperty(aClass, [propertyName cStringUsingEncoding:NSASCIIStringEncoding]);
if(property != NULL)
{
*isGetterPtr = NO;
return property;
}
}
// search through properties with custom getter/setter that corresponds to selector
unsigned int propertiesCount = 0;
objc_property_t *allProperties = class_copyPropertyList(aClass, &propertiesCount);
for(unsigned int i = 0; i < propertiesCount; i++)
{
NSArray *propertyAttributes = [[NSString stringWithCString:property_getAttributes(allProperties[i])
encoding:NSASCIIStringEncoding] componentsSeparatedByString:@","];
for(NSString *attribute in propertyAttributes)
{
if(([attribute hasPrefix:@"G"] || [attribute hasPrefix:@"S"]) &&
[[attribute substringFromIndex:1] isEqualToString:propertyName])
{
*isGetterPtr = ![attribute hasPrefix:@"S"];
property = allProperties[i];
i = propertiesCount;
break;
}
}
}
free(allProperties);
return property;
}
#pragma mark Signatures for blocks
+ (NSMethodSignature *)signatureForBlock:(id)block
{
/* For a more complete implementation of parsing the block data structure see:
*
* https://github.com/ebf/CTObjectiveCRuntimeAdditions/tree/master/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions
*/
struct OCMBlockDef *blockRef = (__bridge struct OCMBlockDef *)block;
if(!(blockRef->flags & OCMBlockDescriptionFlagsHasSignature))
return nil;
void *signatureLocation = blockRef->descriptor;
signatureLocation += sizeof(unsigned long int);
signatureLocation += sizeof(unsigned long int);
if(blockRef->flags & OCMBlockDescriptionFlagsHasCopyDispose)
{
signatureLocation += sizeof(void (*)(void *dst, void *src));
signatureLocation += sizeof(void (*)(void *src));
}
const char *signature = (*(const char **)signatureLocation);
return [NSMethodSignature signatureWithObjCTypes:signature];
}
#pragma mark Extended attributes
- (BOOL)usesSpecialStructureReturn
{
const char *types = OCMTypeWithoutQualifiers([self methodReturnType]);
if((types == NULL) || (types[0] != '{'))
return NO;
/* In some cases structures are returned by ref. The rules are complex and depend on the
architecture, see:
http://sealiesoftware.com/blog/archive/2008/10/30/objc_explain_objc_msgSend_stret.html
http://developer.apple.com/library/mac/#documentation/DeveloperTools/Conceptual/LowLevelABI/000-Introduction/introduction.html
https://github.com/atgreen/libffi/blob/master/src/x86/ffi64.c
http://www.uclibc.org/docs/psABI-x86_64.pdf
http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042e/IHI0042E_aapcs.pdf
NSMethodSignature knows the details but has no API to return it, though it is in
the debugDescription. Horribly kludgy.
*/
NSRange range = [[self debugDescription] rangeOfString:@"is special struct return? YES"];
return range.length > 0;
}
- (NSString *)fullTypeString
{
NSMutableString *typeString = [NSMutableString string];
[typeString appendFormat:@"%s", [self methodReturnType]];
for(NSUInteger i = 0; i < [self numberOfArguments]; i++)
[typeString appendFormat:@"%s", [self getArgumentTypeAtIndex:i]];
return typeString;
}
- (const char *)fullObjCTypes
{
return [[self fullTypeString] UTF8String];
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/NSNotificationCenter+OCMAdditions.h
================================================
/*
* Copyright (c) 2009-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
@class OCObserverMockObject;
@interface NSNotificationCenter(OCMAdditions)
- (void)addMockObserver:(OCObserverMockObject *)notificationObserver name:(NSString *)notificationName object:(id)notificationSender;
@end
================================================
FILE: Pods/OCMock/Source/OCMock/NSNotificationCenter+OCMAdditions.m
================================================
/*
* Copyright (c) 2009-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "NSNotificationCenter+OCMAdditions.h"
#import "OCObserverMockObject.h"
@implementation NSNotificationCenter(OCMAdditions)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- (void)addMockObserver:(OCObserverMockObject *)notificationObserver name:(NSString *)notificationName object:(id)notificationSender
{
[notificationObserver autoRemoveFromCenter:self];
[self addObserver:notificationObserver selector:@selector(handleNotification:) name:notificationName object:notificationSender];
}
#pragma clang diagnostic pop
@end
================================================
FILE: Pods/OCMock/Source/OCMock/NSObject+OCMAdditions.h
================================================
/*
* Copyright (c) 2013-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
@interface NSObject(OCMAdditions)
+ (IMP)instanceMethodForwarderForSelector:(SEL)aSelector;
+ (void)enumerateMethodsInClass:(Class)aClass usingBlock:(void (^)(Class cls, SEL sel))aBlock;
@end
================================================
FILE: Pods/OCMock/Source/OCMock/NSObject+OCMAdditions.m
================================================
/*
* Copyright (c) 2009-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
#import
#import "NSMethodSignature+OCMAdditions.h"
#import "NSObject+OCMAdditions.h"
@implementation NSObject(OCMAdditions)
+ (IMP)instanceMethodForwarderForSelector:(SEL)aSelector
{
#ifndef __arm64__
static NSMutableDictionary *_OCMReturnTypeCache;
if(_OCMReturnTypeCache == nil)
_OCMReturnTypeCache = [[NSMutableDictionary alloc] init];
BOOL needsStructureReturn;
void *rawCacheKey[2] = { (void *)self, aSelector };
NSData *cacheKey = [NSData dataWithBytes:rawCacheKey length:sizeof(rawCacheKey)];
NSNumber *cachedValue = [_OCMReturnTypeCache objectForKey:cacheKey];
if(cachedValue == nil)
{
NSMethodSignature *sig = [self instanceMethodSignatureForSelector:aSelector];
needsStructureReturn = [sig usesSpecialStructureReturn];
[_OCMReturnTypeCache setObject:@(needsStructureReturn) forKey:cacheKey];
}
else
{
needsStructureReturn = [cachedValue boolValue];
}
if(needsStructureReturn)
return (IMP)_objc_msgForward_stret;
#endif
return _objc_msgForward;
}
+ (void)enumerateMethodsInClass:(Class)aClass usingBlock:(void (^)(Class cls, SEL sel))aBlock
{
for(Class cls = aClass; cls != nil; cls = class_getSuperclass(cls))
{
Method *methodList = class_copyMethodList(cls, NULL);
if(methodList == NULL)
continue;
for(Method *mPtr = methodList; *mPtr != NULL; mPtr++)
{
SEL sel = method_getName(*mPtr);
aBlock(cls, sel);
}
free(methodList);
}
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/NSValue+OCMAdditions.h
================================================
/*
* Copyright (c) 2014-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
@interface NSValue(OCMAdditions)
- (BOOL)getBytes:(void *)outputBuf objCType:(const char *)targetType;
@end
================================================
FILE: Pods/OCMock/Source/OCMock/NSValue+OCMAdditions.m
================================================
/*
* Copyright (c) 2014-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "NSValue+OCMAdditions.h"
#import "OCMFunctionsPrivate.h"
@implementation NSValue(OCMAdditions)
static NSNumber *OCMNumberForValue(NSValue *value)
{
#define CREATE_NUM(_type) ({ _type _v; [value getValue:&_v]; @(_v); })
switch([value objCType][0])
{
case 'c': return CREATE_NUM(char);
case 'C': return CREATE_NUM(unsigned char);
case 'B': return CREATE_NUM(bool);
case 's': return CREATE_NUM(short);
case 'S': return CREATE_NUM(unsigned short);
case 'i': return CREATE_NUM(int);
case 'I': return CREATE_NUM(unsigned int);
case 'l': return CREATE_NUM(long);
case 'L': return CREATE_NUM(unsigned long);
case 'q': return CREATE_NUM(long long);
case 'Q': return CREATE_NUM(unsigned long long);
case 'f': return CREATE_NUM(float);
case 'd': return CREATE_NUM(double);
default: return nil;
}
}
- (BOOL)getBytes:(void *)outputBuf objCType:(const char *)targetType
{
/*
* See if they are similar number types, and if we can convert losslessly between them.
* For the most part, we set things up to use CFNumberGetValue, which returns false if
* conversion will be lossy.
*/
CFNumberType inputType = OCMNumberTypeForObjCType([self objCType]);
CFNumberType outputType = OCMNumberTypeForObjCType(targetType);
if(inputType == 0 || outputType == 0) // one or both are non-number types
return NO;
NSNumber *inputNumber = [self isKindOfClass:[NSNumber class]] ? (NSNumber *)self : OCMNumberForValue(self);
/*
* Due to some legacy, back-compatible requirements in CFNumber.c, CFNumberGetValue can return true for
* some conversions which should not be allowed (by reading source, conversions from integer types to
* 8-bit or 16-bit integer types). So, check ourselves.
*/
long long min;
long long max;
long long val = [inputNumber longLongValue];
switch(targetType[0])
{
case 'B':
case 'c': min = CHAR_MIN; max = CHAR_MAX; break;
case 'C': min = 0; max = UCHAR_MAX; break;
case 's': min = SHRT_MIN; max = SHRT_MAX; break;
case 'S': min = 0; max = USHRT_MAX; break;
default: min = LLONG_MIN; max = LLONG_MAX; break;
}
if(val < min || val > max)
return NO;
/* Get the number, and return NO if the value was out of range or conversion was lossy */
return CFNumberGetValue((CFNumberRef)inputNumber, outputType, outputBuf);
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCClassMockObject.h
================================================
/*
* Copyright (c) 2005-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCMockObject.h"
@interface OCClassMockObject : OCMockObject
{
Class mockedClass;
Class originalMetaClass;
Class classCreatedForNewMetaClass;
}
- (id)initWithClass:(Class)aClass;
- (Class)mockedClass;
- (Class)mockObjectClass; // since -class returns the mockedClass
- (void)assertClassIsSupported:(Class)aClass;
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCClassMockObject.m
================================================
/*
* Copyright (c) 2005-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
#import "NSMethodSignature+OCMAdditions.h"
#import "NSObject+OCMAdditions.h"
#import "OCClassMockObject.h"
#import "OCMFunctionsPrivate.h"
#import "OCMInvocationStub.h"
@interface NSObject (OCMClassMockingSupport)
+ (BOOL)supportsMocking:(NSString **)reason;
@end
@implementation OCClassMockObject
#pragma mark Initialisers, description, accessors, etc.
- (id)initWithClass:(Class)aClass
{
[self assertClassIsSupported:aClass];
[super init];
mockedClass = aClass;
[self prepareClassForClassMethodMocking];
return self;
}
- (void)dealloc
{
[self stopMocking];
[super dealloc];
}
- (NSString *)description
{
return [NSString stringWithFormat:@"OCClassMockObject(%@)", NSStringFromClass(mockedClass)];
}
- (Class)mockedClass
{
return mockedClass;
}
- (void)assertClassIsSupported:(Class)aClass
{
if(aClass == Nil)
[NSException raise:NSInvalidArgumentException format:@"Class cannot be Nil."];
if([aClass respondsToSelector:@selector(supportsMocking:)])
{
NSString *reason = nil;
if(![aClass supportsMocking:&reason])
[NSException raise:NSInvalidArgumentException format:@"Class %@ does not support mocking: %@", aClass, reason];
}
}
#pragma mark Extending/overriding superclass behaviour
- (void)stopMocking
{
if(originalMetaClass != nil)
{
[self stopMockingClassMethods];
}
if(classCreatedForNewMetaClass != nil)
{
OCMDisposeSubclass(classCreatedForNewMetaClass);
classCreatedForNewMetaClass = nil;
}
[super stopMocking];
}
- (void)stopMockingClassMethods
{
OCMSetAssociatedMockForClass(nil, mockedClass);
object_setClass(mockedClass, originalMetaClass);
originalMetaClass = nil;
/* created meta class will be disposed later because partial mocks create another subclass depending on it */
}
- (void)addStub:(OCMInvocationStub *)aStub
{
[super addStub:aStub];
if([aStub recordedAsClassMethod])
[self setupForwarderForClassMethodSelector:[[aStub recordedInvocation] selector]];
}
#pragma mark Class method mocking
- (void)prepareClassForClassMethodMocking
{
/* the runtime and OCMock depend on string and array; we don't intercept methods on them to avoid endless loops */
if([[mockedClass class] isSubclassOfClass:[NSString class]] || [[mockedClass class] isSubclassOfClass:[NSArray class]])
return;
/* trying to replace class methods on NSManagedObject and subclasses of it doesn't work; see #339 */
if([mockedClass isSubclassOfClass:objc_getClass("NSManagedObject")])
return;
/* if there is another mock for this exact class, stop it */
id otherMock = OCMGetAssociatedMockForClass(mockedClass, NO);
if(otherMock != nil)
[otherMock stopMockingClassMethods];
OCMSetAssociatedMockForClass(self, mockedClass);
/* dynamically create a subclass and use its meta class as the meta class for the mocked class */
classCreatedForNewMetaClass = OCMCreateSubclass(mockedClass, mockedClass);
originalMetaClass = object_getClass(mockedClass);
id newMetaClass = object_getClass(classCreatedForNewMetaClass);
/* create a dummy initialize method */
Method myDummyInitializeMethod = class_getInstanceMethod([self mockObjectClass], @selector(initializeForClassObject));
const char *initializeTypes = method_getTypeEncoding(myDummyInitializeMethod);
IMP myDummyInitializeIMP = method_getImplementation(myDummyInitializeMethod);
class_addMethod(newMetaClass, @selector(initialize), myDummyInitializeIMP, initializeTypes);
object_setClass(mockedClass, newMetaClass); // only after dummy initialize is installed (iOS9)
/* point forwardInvocation: of the object to the implementation in the mock */
Method myForwardMethod = class_getInstanceMethod([self mockObjectClass], @selector(forwardInvocationForClassObject:));
IMP myForwardIMP = method_getImplementation(myForwardMethod);
class_addMethod(newMetaClass, @selector(forwardInvocation:), myForwardIMP, method_getTypeEncoding(myForwardMethod));
/* adding forwarder for most class methods (instance methods on meta class) to allow for verify after run */
NSArray *methodsNotToForward = @[
@"class", @"forwardingTargetForSelector:", @"methodSignatureForSelector:", @"forwardInvocation:", @"isBlock",
@"instanceMethodForwarderForSelector:", @"instanceMethodSignatureForSelector:", @"resolveClassMethod:"
];
void (^setupForwarderFiltered)(Class, SEL) = ^(Class cls, SEL sel) {
if((cls == object_getClass([NSObject class])) || (cls == [NSObject class]) || (cls == object_getClass(cls)))
return;
if(OCMIsApplePrivateMethod(cls, sel))
return;
if([methodsNotToForward containsObject:NSStringFromSelector(sel)])
return;
@try
{
[self setupForwarderForClassMethodSelector:sel];
}
@catch(NSException *e)
{
// ignore for now
}
};
[NSObject enumerateMethodsInClass:originalMetaClass usingBlock:setupForwarderFiltered];
}
- (void)setupForwarderForClassMethodSelector:(SEL)selector
{
SEL aliasSelector = OCMAliasForOriginalSelector(selector);
if(class_getClassMethod(mockedClass, aliasSelector) != NULL)
return;
Method originalMethod = class_getClassMethod(mockedClass, selector);
IMP originalIMP = method_getImplementation(originalMethod);
const char *types = method_getTypeEncoding(originalMethod);
Class metaClass = object_getClass(mockedClass);
IMP forwarderIMP = [originalMetaClass instanceMethodForwarderForSelector:selector];
class_addMethod(metaClass, aliasSelector, originalIMP, types);
class_replaceMethod(metaClass, selector, forwarderIMP, types);
}
- (void)forwardInvocationForClassObject:(NSInvocation *)anInvocation
{
// in here "self" is a reference to the real class, not the mock
OCClassMockObject *mock = OCMGetAssociatedMockForClass((Class)self, YES);
if(mock == nil)
{
[NSException raise:NSInternalInconsistencyException format:@"No mock for class %@", NSStringFromClass((Class)self)];
}
if([mock handleInvocation:anInvocation] == NO)
{
[anInvocation setSelector:OCMAliasForOriginalSelector([anInvocation selector])];
[anInvocation invoke];
}
}
- (void)initializeForClassObject
{
// we really just want to have an implementation so that the superclass's is not called
}
#pragma mark Proxy API
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *signature = [mockedClass instanceMethodSignatureForSelector:aSelector];
if(signature == nil)
{
signature = [NSMethodSignature signatureForDynamicPropertyAccessedWithSelector:aSelector inClass:mockedClass];
}
return signature;
}
- (Class)mockObjectClass
{
return [super class];
}
- (Class)class
{
return mockedClass;
}
- (BOOL)respondsToSelector:(SEL)selector
{
return [mockedClass instancesRespondToSelector:selector];
}
- (BOOL)isKindOfClass:(Class)aClass
{
return [mockedClass isSubclassOfClass:aClass];
}
- (BOOL)conformsToProtocol:(Protocol *)aProtocol
{
Class clazz = mockedClass;
while(clazz != nil)
{
if(class_conformsToProtocol(clazz, aProtocol))
{
return YES;
}
clazz = class_getSuperclass(clazz);
}
return NO;
}
@end
#pragma mark -
/*
taken from:
`class-dump -f isNS /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/System/Library/Frameworks/CoreFoundation.framework`
@ interface NSObject (__NSIsKinds)
- (_Bool)isNSValue__;
- (_Bool)isNSTimeZone__;
- (_Bool)isNSString__;
- (_Bool)isNSSet__;
- (_Bool)isNSOrderedSet__;
- (_Bool)isNSNumber__;
- (_Bool)isNSDictionary__;
- (_Bool)isNSDate__;
- (_Bool)isNSData__;
- (_Bool)isNSArray__;
*/
@implementation OCClassMockObject(NSIsKindsImplementation)
- (BOOL)isNSValue__
{
return [mockedClass isSubclassOfClass:[NSValue class]];
}
- (BOOL)isNSTimeZone__
{
return [mockedClass isSubclassOfClass:[NSTimeZone class]];
}
- (BOOL)isNSSet__
{
return [mockedClass isSubclassOfClass:[NSSet class]];
}
- (BOOL)isNSOrderedSet__
{
return [mockedClass isSubclassOfClass:[NSOrderedSet class]];
}
- (BOOL)isNSNumber__
{
return [mockedClass isSubclassOfClass:[NSNumber class]];
}
- (BOOL)isNSDate__
{
return [mockedClass isSubclassOfClass:[NSDate class]];
}
- (BOOL)isNSString__
{
return [mockedClass isSubclassOfClass:[NSString class]];
}
- (BOOL)isNSDictionary__
{
return [mockedClass isSubclassOfClass:[NSDictionary class]];
}
- (BOOL)isNSData__
{
return [mockedClass isSubclassOfClass:[NSData class]];
}
- (BOOL)isNSArray__
{
return [mockedClass isSubclassOfClass:[NSArray class]];
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMArg.h
================================================
/*
* Copyright (c) 2009-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
@interface OCMArg : NSObject
// constraining arguments
+ (id)any;
+ (SEL)anySelector;
+ (void *)anyPointer;
+ (id __autoreleasing *)anyObjectRef;
+ (id)isNil;
+ (id)isNotNil;
+ (id)isEqual:(id)value;
+ (id)isNotEqual:(id)value;
+ (id)isKindOfClass:(Class)cls;
+ (id)checkWithSelector:(SEL)selector onObject:(id)anObject;
+ (id)checkWithBlock:(BOOL (^)(id obj))block;
// manipulating arguments
+ (id *)setTo:(id)value;
+ (void *)setToValue:(NSValue *)value;
+ (id)invokeBlock;
+ (id)invokeBlockWithArgs:(id)first, ... NS_REQUIRES_NIL_TERMINATION;
+ (id)defaultValue;
// internal use only
+ (id)resolveSpecialValues:(NSValue *)value;
@end
#define OCMOCK_ANY [OCMArg any]
#define OCMOCK_VALUE(variable) \
({ __typeof__(variable) __v = (variable); [NSValue value:&__v withObjCType:@encode(__typeof__(__v))]; })
================================================
FILE: Pods/OCMock/Source/OCMock/OCMArg.m
================================================
/*
* Copyright (c) 2009-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
#import "OCMArg.h"
#import "OCMBlockArgCaller.h"
#import "OCMConstraint.h"
#import "OCMPassByRefSetter.h"
@implementation OCMArg
+ (id)any
{
return [OCMAnyConstraint constraint];
}
+ (void *)anyPointer
{
return (void *)0x01234567;
}
+ (id __autoreleasing *)anyObjectRef
{
return (id *)[self anyPointer];
}
+ (SEL)anySelector
{
return NSSelectorFromString(@"aSelectorThatMatchesAnySelector");
}
+ (id)isNil
{
return [OCMIsNilConstraint constraint];
}
+ (id)isNotNil
{
return [OCMIsNotNilConstraint constraint];
}
+ (id)isEqual:(id)value
{
return value;
}
+ (id)isNotEqual:(id)value
{
OCMIsNotEqualConstraint *constraint = [OCMIsNotEqualConstraint constraint];
constraint->testValue = value;
return constraint;
}
+ (id)isKindOfClass:(Class)cls
{
return [[[OCMBlockConstraint alloc] initWithConstraintBlock:^BOOL(id obj) {
return [obj isKindOfClass:cls];
}] autorelease];
}
+ (id)checkWithSelector:(SEL)selector onObject:(id)anObject
{
return [OCMConstraint constraintWithSelector:selector onObject:anObject];
}
+ (id)checkWithBlock:(BOOL (^)(id))block
{
return [[[OCMBlockConstraint alloc] initWithConstraintBlock:block] autorelease];
}
+ (id *)setTo:(id)value
{
return (id *)[[[OCMPassByRefSetter alloc] initWithValue:value] autorelease];
}
+ (void *)setToValue:(NSValue *)value
{
return (id *)[[[OCMPassByRefSetter alloc] initWithValue:value] autorelease];
}
+ (id)invokeBlock
{
return [[[OCMBlockArgCaller alloc] init] autorelease];
}
+ (id)invokeBlockWithArgs:(id)first, ... NS_REQUIRES_NIL_TERMINATION
{
NSMutableArray *params = [NSMutableArray array];
va_list args;
if(first)
{
[params addObject:first];
va_start(args, first);
id obj;
while((obj = va_arg(args, id)))
{
[params addObject:obj];
}
va_end(args);
}
return [[[OCMBlockArgCaller alloc] initWithBlockArguments:params] autorelease];
}
+ (id)defaultValue
{
return [NSNull null];
}
+ (id)resolveSpecialValues:(NSValue *)value
{
const char *type = [value objCType];
if(type[0] == '^')
{
void *pointer = [value pointerValue];
if(pointer == [self anyPointer])
return [OCMArg any];
if((pointer != NULL) && [OCMPassByRefSetter isPassByRefSetterInstance:pointer])
return (id)pointer;
}
else if(type[0] == ':')
{
SEL selector;
[value getValue:&selector];
if(selector == NSSelectorFromString(@"aSelectorThatMatchesAnySelector"))
return [OCMArg any];
}
return value;
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMArgAction.h
================================================
/*
* Copyright (c) 2015-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
@interface OCMArgAction : NSObject
- (void)handleArgument:(id)argument;
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMArgAction.m
================================================
/*
* Copyright (c) 2015-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCMArgAction.h"
@implementation OCMArgAction
- (void)handleArgument:(id)argument
{
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMBlockArgCaller.h
================================================
/*
* Copyright (c) 2015-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCMArgAction.h"
@interface OCMBlockArgCaller : OCMArgAction
{
NSArray *arguments;
}
- (instancetype)initWithBlockArguments:(NSArray *)someArgs;
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMBlockArgCaller.m
================================================
/*
* Copyright (c) 2015-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCMBlockArgCaller.h"
#import "NSInvocation+OCMAdditions.h"
@implementation OCMBlockArgCaller
- (instancetype)initWithBlockArguments:(NSArray *)someArgs
{
self = [super init];
if(self)
{
arguments = [someArgs copy];
}
return self;
}
- (void)dealloc
{
[arguments release];
[super dealloc];
}
- (id)copyWithZone:(NSZone *)zone
{
return [self retain];
}
- (void)handleArgument:(id)aBlock
{
if(aBlock)
{
NSInvocation *inv = [NSInvocation invocationForBlock:aBlock withArguments:arguments];
[inv invokeWithTarget:aBlock];
}
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMBlockCaller.h
================================================
/*
* Copyright (c) 2010-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
@interface OCMBlockCaller : NSObject
{
void (^block)(NSInvocation *);
}
- (id)initWithCallBlock:(void (^)(NSInvocation *))theBlock;
- (void)handleInvocation:(NSInvocation *)anInvocation;
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMBlockCaller.m
================================================
/*
* Copyright (c) 2010-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCMBlockCaller.h"
@implementation OCMBlockCaller
- (id)initWithCallBlock:(void (^)(NSInvocation *))theBlock
{
if((self = [super init]))
{
block = [theBlock copy];
}
return self;
}
- (void)dealloc
{
[block release];
[super dealloc];
}
- (void)handleInvocation:(NSInvocation *)anInvocation
{
if(block != nil)
{
block(anInvocation);
}
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMBoxedReturnValueProvider.h
================================================
/*
* Copyright (c) 2009-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCMObjectReturnValueProvider.h"
@interface OCMBoxedReturnValueProvider : OCMObjectReturnValueProvider
{
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMBoxedReturnValueProvider.m
================================================
/*
* Copyright (c) 2009-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCMBoxedReturnValueProvider.h"
#import "NSValue+OCMAdditions.h"
#import "OCMFunctionsPrivate.h"
@implementation OCMBoxedReturnValueProvider
- (void)handleInvocation:(NSInvocation *)anInvocation
{
NSUInteger valueSize = 0;
NSValue *returnValueAsNSValue = (NSValue *)returnValue;
NSGetSizeAndAlignment([returnValueAsNSValue objCType], &valueSize, NULL);
char valueBuffer[valueSize];
[returnValueAsNSValue getValue:valueBuffer];
const char *returnType = [[anInvocation methodSignature] methodReturnType];
if([self isMethodReturnType:returnType compatibleWithValueType:[returnValueAsNSValue objCType]
value:valueBuffer valueSize:valueSize])
{
[anInvocation setReturnValue:valueBuffer];
}
else if([returnValueAsNSValue getBytes:valueBuffer objCType:returnType])
{
[anInvocation setReturnValue:valueBuffer];
}
else
{
[NSException raise:NSInvalidArgumentException
format:@"Return value cannot be used for method; method signature declares '%s' but value is '%s'.", returnType, [returnValueAsNSValue objCType]];
}
}
- (BOOL)isMethodReturnType:(const char *)returnType compatibleWithValueType:(const char *)valueType value:(const void *)value valueSize:(size_t)valueSize
{
/* Same types are obviously compatible */
if(strcmp(returnType, valueType) == 0)
return YES;
/* Special treatment for nil and Nil */
if(strcmp(returnType, @encode(id)) == 0 || strcmp(returnType, @encode(Class)) == 0)
return OCMIsNilValue(valueType, value, valueSize);
return OCMEqualTypesAllowingOpaqueStructs(returnType, valueType);
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMConstraint.h
================================================
/*
* Copyright (c) 2007-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
@interface OCMConstraint : NSObject
+ (instancetype)constraint;
- (BOOL)evaluate:(id)value;
// if you are looking for any, isNil, etc, they have moved to OCMArg
// try to use [OCMArg checkWith...] instead of the constraintWith... methods below
+ (instancetype)constraintWithSelector:(SEL)aSelector onObject:(id)anObject;
+ (instancetype)constraintWithSelector:(SEL)aSelector onObject:(id)anObject withValue:(id)aValue;
@end
@interface OCMAnyConstraint : OCMConstraint
@end
@interface OCMIsNilConstraint : OCMConstraint
@end
@interface OCMIsNotNilConstraint : OCMConstraint
@end
@interface OCMIsNotEqualConstraint : OCMConstraint
{
@public
id testValue;
}
@end
@interface OCMInvocationConstraint : OCMConstraint
{
@public
NSInvocation *invocation;
}
@end
@interface OCMBlockConstraint : OCMConstraint
{
BOOL (^block)(id);
}
- (instancetype)initWithConstraintBlock:(BOOL (^)(id))block;
@end
#ifndef OCM_DISABLE_SHORT_SYNTAX
#define CONSTRAINT(aSelector) [OCMConstraint constraintWithSelector:aSelector onObject:self]
#define CONSTRAINTV(aSelector, aValue) [OCMConstraint constraintWithSelector:aSelector onObject:self withValue:(aValue)]
#endif
================================================
FILE: Pods/OCMock/Source/OCMock/OCMConstraint.m
================================================
/*
* Copyright (c) 2007-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
#import "OCMConstraint.h"
@implementation OCMConstraint
+ (instancetype)constraint
{
return [[[self alloc] init] autorelease];
}
- (BOOL)evaluate:(id)value
{
return NO;
}
- (id)copyWithZone:(struct _NSZone *)zone __unused
{
return [self retain];
}
+ (instancetype)constraintWithSelector:(SEL)aSelector onObject:(id)anObject
{
OCMInvocationConstraint *constraint = [OCMInvocationConstraint constraint];
NSMethodSignature *signature = [anObject methodSignatureForSelector:aSelector];
if(signature == nil)
[NSException raise:NSInvalidArgumentException
format:@"Unknown selector %@ used in constraint.", NSStringFromSelector(aSelector)];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:anObject];
[invocation setSelector:aSelector];
constraint->invocation = invocation;
return constraint;
}
+ (instancetype)constraintWithSelector:(SEL)aSelector onObject:(id)anObject withValue:(id)aValue
{
OCMInvocationConstraint *constraint = (OCMInvocationConstraint *)[self constraintWithSelector:aSelector onObject:anObject];
if([[constraint->invocation methodSignature] numberOfArguments] < 4)
[NSException raise:NSInvalidArgumentException format:@"Constraint with value requires selector with two arguments."];
[constraint->invocation setArgument:&aValue atIndex:3];
return constraint;
}
@end
#pragma mark -
@implementation OCMAnyConstraint
- (BOOL)evaluate:(id)value
{
return YES;
}
@end
#pragma mark -
@implementation OCMIsNilConstraint
- (BOOL)evaluate:(id)value
{
return value == nil;
}
@end
#pragma mark -
@implementation OCMIsNotNilConstraint
- (BOOL)evaluate:(id)value
{
return value != nil;
}
@end
#pragma mark -
@implementation OCMIsNotEqualConstraint
- (BOOL)evaluate:(id)value
{
return ![value isEqual:testValue];
}
@end
#pragma mark -
@implementation OCMInvocationConstraint
- (BOOL)evaluate:(id)value
{
[invocation setArgument:&value atIndex:2]; // should test if constraint takes arg
[invocation invoke];
BOOL returnValue;
[invocation getReturnValue:&returnValue];
return returnValue;
}
@end
#pragma mark -
@implementation OCMBlockConstraint
- (instancetype)initWithConstraintBlock:(BOOL (^)(id))aBlock
{
if((self = [super init]))
{
block = [aBlock copy];
}
return self;
}
- (void)dealloc
{
[block release];
[super dealloc];
}
- (BOOL)evaluate:(id)value
{
return block ? block(value) : NO;
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMExceptionReturnValueProvider.h
================================================
/*
* Copyright (c) 2009-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCMObjectReturnValueProvider.h"
extern NSString *OCMStubbedException;
@interface OCMExceptionReturnValueProvider : OCMObjectReturnValueProvider
{
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMExceptionReturnValueProvider.m
================================================
/*
* Copyright (c) 2009-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCMExceptionReturnValueProvider.h"
@implementation OCMExceptionReturnValueProvider
NSString *OCMStubbedException = @"OCMStubbedException";
- (void)handleInvocation:(NSInvocation *)anInvocation
{
[[NSException exceptionWithName:OCMStubbedException reason:@"Exception stubbed in test." userInfo:@{ @"exception" : returnValue }] raise];
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMExpectationRecorder.h
================================================
/*
* Copyright (c) 2004-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCMStubRecorder.h"
@interface OCMExpectationRecorder : OCMStubRecorder
- (id)never;
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMExpectationRecorder.m
================================================
/*
* Copyright (c) 2004-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCMExpectationRecorder.h"
#import "OCMInvocationExpectation.h"
#import "OCMockObject.h"
@implementation OCMExpectationRecorder
#pragma mark Initialisers, description, accessors, etc.
- (id)init
{
self = [super init];
[invocationMatcher release];
invocationMatcher = [[OCMInvocationExpectation alloc] init];
return self;
}
- (OCMInvocationExpectation *)expectation
{
return (OCMInvocationExpectation *)invocationMatcher;
}
#pragma mark Modifying the expectation
- (id)never
{
[[self expectation] setMatchAndReject:YES];
return self;
}
#pragma mark Finishing recording
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
[super forwardInvocation:anInvocation];
[mockObject addExpectation:[self expectation]];
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMFunctions.h
================================================
/*
* Copyright (c) 2014-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
#if defined(__cplusplus)
#define OCMOCK_EXTERN extern "C"
#else
#define OCMOCK_EXTERN extern
#endif
OCMOCK_EXTERN BOOL OCMIsObjectType(const char *objCType);
OCMOCK_EXTERN BOOL OCMIsSubclassOfMockClass(Class cls);
================================================
FILE: Pods/OCMock/Source/OCMock/OCMFunctions.m
================================================
/*
* Copyright (c) 2014-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
#if !defined(OCM_DISABLE_XCTEST_FEATURES)
#import
#endif
#import "OCClassMockObject.h"
#import "OCMFunctionsPrivate.h"
#import "OCMLocation.h"
#import "OCPartialMockObject.h"
#pragma mark Known private API
@interface NSException(OCMKnownExceptionMethods)
+ (NSException *)failureInFile:(NSString *)file atLine:(int)line withDescription:(NSString *)formatString, ...;
@end
@interface NSObject(OCMKnownTestCaseMethods)
- (void)recordFailureWithDescription:(NSString *)description inFile:(NSString *)file atLine:(NSUInteger)line expected:(BOOL)expected;
- (void)failWithException:(NSException *)exception;
@end
#pragma mark Functions related to ObjC type system
const char *OCMTypeWithoutQualifiers(const char *objCType)
{
// In certain cases byref appears to just pass the "R" and not the "@" as expected. This is
// likely a bug in the compiler since byref is basically a dead keyword at this point. That
// being said, this will protect us, and returns what would be the expected type.
if(strcmp(objCType, "R") == 0)
return "@";
while(objCType[0] && strchr("rnNoORV", objCType[0]) != NULL)
objCType += 1;
return objCType;
}
static BOOL OCMIsUnqualifiedClassType(const char *unqualifiedObjCType)
{
return (strcmp(unqualifiedObjCType, @encode(Class)) == 0);
}
static BOOL OCMIsUnqualifiedBlockType(const char *unqualifiedObjCType)
{
char blockType[] = @encode(void (^)(void));
if(strcmp(unqualifiedObjCType, blockType) == 0)
return YES;
// sometimes block argument/return types are tacked onto the type, in angle brackets
if(strncmp(unqualifiedObjCType, blockType, sizeof(blockType) - 1) == 0 && unqualifiedObjCType[sizeof(blockType) - 1] == '<')
return YES;
return NO;
}
BOOL OCMIsClassType(const char *objCType)
{
return OCMIsUnqualifiedClassType(OCMTypeWithoutQualifiers(objCType));
}
BOOL OCMIsBlockType(const char *objCType)
{
return OCMIsUnqualifiedBlockType(OCMTypeWithoutQualifiers(objCType));
}
BOOL OCMIsObjectType(const char *objCType)
{
const char *unqualifiedObjCType = OCMTypeWithoutQualifiers(objCType);
char objectType[] = @encode(id);
if(strcmp(unqualifiedObjCType, objectType) == 0 || OCMIsUnqualifiedClassType(unqualifiedObjCType))
return YES;
// sometimes the name of an object's class is tacked onto the type, in double quotes
if(strncmp(unqualifiedObjCType, objectType, sizeof(objectType) - 1) == 0 && unqualifiedObjCType[sizeof(objectType) - 1] == '"')
return YES;
// if the returnType is a typedef to an object, it has the form ^{OriginClass=#}
NSString *regexString = @"^\\^\\{(.*)=#.*\\}";
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regexString options:0 error:NULL];
NSString *type = [NSString stringWithCString:unqualifiedObjCType encoding:NSASCIIStringEncoding];
if([regex numberOfMatchesInString:type options:0 range:NSMakeRange(0, type.length)] > 0)
return YES;
// if the return type is a block we treat it like an object
return OCMIsUnqualifiedBlockType(unqualifiedObjCType);
}
BOOL OCMIsPointerType(const char *objCType)
{
const char *unqualifiedObjCType = OCMTypeWithoutQualifiers(objCType);
if(unqualifiedObjCType[0] == '^')
return YES;
if(unqualifiedObjCType[0] == '*')
return YES;
return NO;
}
CFNumberType OCMNumberTypeForObjCType(const char *objcType)
{
switch(objcType[0])
{
case 'c': return kCFNumberCharType;
case 'C': return kCFNumberCharType;
case 'B': return kCFNumberCharType;
case 's': return kCFNumberShortType;
case 'S': return kCFNumberShortType;
case 'i': return kCFNumberIntType;
case 'I': return kCFNumberIntType;
case 'l': return kCFNumberLongType;
case 'L': return kCFNumberLongType;
case 'q': return kCFNumberLongLongType;
case 'Q': return kCFNumberLongLongType;
case 'f': return kCFNumberFloatType;
case 'd': return kCFNumberDoubleType;
default: return 0;
}
}
static BOOL ParseStructType(const char *type, const char **typeEnd, const char **typeNameEnd, const char **typeEqualSign)
{
if(type[0] != '{' && type[0] != '(')
return NO;
*typeNameEnd = NULL;
*typeEqualSign = NULL;
const char endChar = type[0] == '{' ? '}' : ')';
for(const char *ptr = type + 1; *ptr; ++ptr)
{
switch(*ptr)
{
case '(':
case '{':
{
const char *subTypeEnd;
const char *subTypeNameEnd;
const char *subTypeEqualSign;
if(!ParseStructType(ptr, &subTypeEnd, &subTypeNameEnd, &subTypeEqualSign))
return NO;
ptr = subTypeEnd;
break;
}
case '=':
{
if(!*typeEqualSign)
{
*typeNameEnd = ptr;
*typeEqualSign = ptr;
}
break;
}
case ')':
case '}':
{
if(*ptr == endChar)
{
*typeEnd = ptr;
if(!*typeNameEnd)
*typeNameEnd = ptr;
return YES;
}
break;
}
default:
break;
}
}
return NO;
}
/*
* Sometimes an external type is an opaque struct (which will have an @encode of "{structName}"
* or "{structName=}") but the actual method return type, or property type, will know the contents
* of the struct (so will have an objcType of say "{structName=iiSS}". This function will determine
* those are equal provided they have the same structure name, otherwise everything else will be
* compared textually. This can happen particularly for pointers to such structures, which still
* encode what is being pointed to.
*
* In addition, this funtion will consider structures with unknown names, encoded as "{?=}, equal to
* structures with any name. This means that "{?=dd}" and "{foo=dd}", and even "{?=}" and "{foo=dd}",
* are considered equal.
*
* For some types some runtime functions throw exceptions, which is why we wrap this in an
* exception handler just below.
*/
static BOOL OCMEqualTypesAllowingOpaqueStructsInternal(const char *type1, const char *type2)
{
type1 = OCMTypeWithoutQualifiers(type1);
type2 = OCMTypeWithoutQualifiers(type2);
switch(type1[0])
{
case '{':
case '(':
{
if(type2[0] != type1[0])
return NO;
const char *type1End;
const char *type1NameEnd;
const char *type1EqualSign;
if(!ParseStructType(type1, &type1End, &type1NameEnd, &type1EqualSign))
return NO;
const char *type2End;
const char *type2NameEnd;
const char *type2EqualSign;
if(!ParseStructType(type2, &type2End, &type2NameEnd, &type2EqualSign))
return NO;
/* Opaque types either don't have an equals sign (just the name and the end brace), or
* empty content after the equals sign.
* We want that to compare the same as a type of the same name but with the content.
*/
BOOL type1Opaque = (type1EqualSign == NULL || type1EqualSign + 1 == type1End);
BOOL type2Opaque = (type2EqualSign == NULL || type2EqualSign + 2 == type2End);
intptr_t type1NameLen = type1NameEnd - type1;
intptr_t type2NameLen = type2NameEnd - type2;
/* If the names are not equal and neither of the names is a question mark, return NO */
if((type1NameLen != type2NameLen || strncmp(type1, type2, type1NameLen)) &&
!((type1NameLen == 2) && (type1[1] == '?')) && !((type2NameLen == 2) && (type2[1] == '?')) &&
!(type1NameLen == 1 || type2NameLen == 1))
return NO;
/* If the same name, and at least one is opaque, that is close enough. */
if(type1Opaque || type2Opaque)
return YES;
/* Otherwise, compare all the elements. Use NSGetSizeAndAlignment to walk through the struct elements. */
type1 = type1EqualSign + 1;
type2 = type2EqualSign + 1;
while(type1 != type1End && *type1)
{
if(!OCMEqualTypesAllowingOpaqueStructs(type1, type2))
return NO;
if(*type1 != '{' && *type1 != '(')
{
type1 = NSGetSizeAndAlignment(type1, NULL, NULL);
type2 = NSGetSizeAndAlignment(type2, NULL, NULL);
}
else
{
const char *subType1End;
const char *subType1NameEnd;
const char *subType1EqualSign;
if(!ParseStructType(type1, &subType1End, &subType1NameEnd, &subType1EqualSign))
return NO;
const char *subType2End;
const char *subType2NameEnd;
const char *subType2EqualSign;
if(!ParseStructType(type2, &subType2End, &subType2NameEnd, &subType2EqualSign))
return NO;
type1 = subType1End + 1;
type2 = subType2End + 1;
}
}
return YES;
}
case '^':
/* for a pointer, make sure the other is a pointer, then recursively compare the rest */
if(type2[0] != type1[0])
return NO;
return OCMEqualTypesAllowingOpaqueStructs(type1 + 1, type2 + 1);
case '?':
return type2[0] == '?';
case '\0':
return type2[0] == '\0';
default:
{
// Move the type pointers past the current types, then compare that region
const char *afterType1 = NSGetSizeAndAlignment(type1, NULL, NULL);
const char *afterType2 = NSGetSizeAndAlignment(type2, NULL, NULL);
intptr_t type1Len = afterType1 - type1;
intptr_t type2Len = afterType2 - type2;
return (type1Len == type2Len && (strncmp(type1, type2, type1Len) == 0));
}
}
}
BOOL OCMEqualTypesAllowingOpaqueStructs(const char *type1, const char *type2)
{
@try
{
return OCMEqualTypesAllowingOpaqueStructsInternal(type1, type2);
}
@catch(NSException *e)
{
/* Probably a bitfield or something that NSGetSizeAndAlignment chokes on, oh well */
return NO;
}
}
BOOL OCMIsNilValue(const char *objectCType, const void *value, size_t valueSize)
{
// First, check value itself
for(size_t i = 0; i < valueSize; i++)
if(((const char *)value)[i] != 0)
return NO;
// Depending on the compilation settings of the file where the return value gets recorded,
// nil and Nil get potentially different encodings. Check all known encodings.
if((strcmp(objectCType, @encode(void *)) == 0) || // Standard Objective-C
(strcmp(objectCType, @encode(int)) == 0) || // 32 bit C++ (before nullptr)
(strcmp(objectCType, @encode(long long)) == 0) || // 64 bit C++ (before nullptr)
(strcmp(objectCType, @encode(char *)) == 0)) // C++ with nullptr
return YES;
return NO;
}
BOOL OCMIsAppleBaseClass(Class cls)
{
return (cls == [NSObject class]) || (cls == [NSProxy class]);
}
BOOL OCMIsApplePrivateMethod(Class cls, SEL sel)
{
NSString *className = NSStringFromClass(cls);
NSString *selName = NSStringFromSelector(sel);
return ([className hasPrefix:@"NS"] || [className hasPrefix:@"UI"]) &&
([selName hasPrefix:@"_"] || [selName hasSuffix:@"_"]);
}
BOOL OCMIsBlock(id potentialBlock)
{
static Class blockClass;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
blockClass = [^{} class];
Class nsObjectClass = [NSObject class];
while([blockClass superclass] != nsObjectClass)
{
blockClass = [blockClass superclass];
NSCAssert(blockClass != nil, @"Blocks are expected to inherit from NSObject.");
}
});
return [potentialBlock isKindOfClass:blockClass];
}
BOOL OCMIsNonEscapingBlock(id block)
{
struct OCMBlockDef *blockRef = (__bridge struct OCMBlockDef *)block;
return OCMIsBlock(block) && (blockRef->flags & OCMBlockIsNoEscape) != 0;
}
#pragma mark Creating and disposing classes
static NSString *const OCMSubclassPrefix = @"OCMock_";
Class OCMCreateSubclass(Class class, void *ref)
{
const char *className = [[NSString stringWithFormat:@"%@%@-%p-%u", OCMSubclassPrefix, NSStringFromClass(class), ref, arc4random()] UTF8String];
Class subclass = objc_allocateClassPair(class, className, 0);
objc_registerClassPair(subclass);
return subclass;
}
void OCMDisposeSubclass(Class cls)
{
if(!OCMIsMockSubclass(cls))
{
[NSException raise:NSInvalidArgumentException format:@"Not a mock subclass; found %@\nThe subclass dynamically created by OCMock has been replaced by another class. This can happen when KVO or CoreData create their own dynamic subclass after OCMock created its subclass.\nYou will need to reorder initialization and/or teardown so that classes are created and disposed of in the right order.", NSStringFromClass(cls)];
}
objc_disposeClassPair(cls);
}
BOOL OCMIsMockSubclass(Class cls)
{
return [NSStringFromClass(cls) hasPrefix:OCMSubclassPrefix];
}
BOOL OCMIsSubclassOfMockClass(Class cls)
{
for(; cls != nil; cls = class_getSuperclass(cls))
{
if(OCMIsMockSubclass(cls))
return YES;
}
return NO;
}
#pragma mark Alias for renaming real methods
static NSString *const OCMRealMethodAliasPrefix = @"ocmock_replaced_";
static const char *const OCMRealMethodAliasPrefixCString = "ocmock_replaced_";
BOOL OCMIsAliasSelector(SEL selector)
{
return [NSStringFromSelector(selector) hasPrefix:OCMRealMethodAliasPrefix];
}
SEL OCMAliasForOriginalSelector(SEL selector)
{
char aliasName[2048];
const char *originalName = sel_getName(selector);
strlcpy(aliasName, OCMRealMethodAliasPrefixCString, sizeof(aliasName));
strlcat(aliasName, originalName, sizeof(aliasName));
return sel_registerName(aliasName);
}
SEL OCMOriginalSelectorForAlias(SEL selector)
{
if(!OCMIsAliasSelector(selector))
[NSException raise:NSInvalidArgumentException format:@"Not an alias selector; found %@", NSStringFromSelector(selector)];
NSString *string = NSStringFromSelector(selector);
return NSSelectorFromString([string substringFromIndex:[OCMRealMethodAliasPrefix length]]);
}
#pragma mark Wrappers around associative references
static NSString *const OCMClassMethodMockObjectKey = @"OCMClassMethodMockObjectKey";
void OCMSetAssociatedMockForClass(OCClassMockObject *mock, Class aClass)
{
if((mock != nil) && (objc_getAssociatedObject(aClass, OCMClassMethodMockObjectKey) != nil))
[NSException raise:NSInternalInconsistencyException format:@"Another mock is already associated with class %@", NSStringFromClass(aClass)];
objc_setAssociatedObject(aClass, OCMClassMethodMockObjectKey, mock, OBJC_ASSOCIATION_ASSIGN);
}
OCClassMockObject *OCMGetAssociatedMockForClass(Class aClass, BOOL includeSuperclasses)
{
OCClassMockObject *mock = nil;
do
{
mock = objc_getAssociatedObject(aClass, OCMClassMethodMockObjectKey);
aClass = class_getSuperclass(aClass);
}
while((mock == nil) && (aClass != nil) && includeSuperclasses);
return mock;
}
static NSString *const OCMPartialMockObjectKey = @"OCMPartialMockObjectKey";
void OCMSetAssociatedMockForObject(OCClassMockObject *mock, id anObject)
{
if((mock != nil) && (objc_getAssociatedObject(anObject, OCMPartialMockObjectKey) != nil))
[NSException raise:NSInternalInconsistencyException format:@"Another mock is already associated with object %@", anObject];
objc_setAssociatedObject(anObject, OCMPartialMockObjectKey, mock, OBJC_ASSOCIATION_ASSIGN);
}
OCPartialMockObject *OCMGetAssociatedMockForObject(id anObject)
{
return objc_getAssociatedObject(anObject, OCMPartialMockObjectKey);
}
#pragma mark Functions related to IDE error reporting
void OCMReportFailure(OCMLocation *loc, NSString *description)
{
id testCase = [loc testCase];
#ifdef __IPHONE_14_0 // this is actually a test for Xcode 12; see issue #472
#if !defined(OCM_DISABLE_XCTEST_FEATURES)
if((testCase != nil) && [testCase respondsToSelector:@selector(recordIssue:)])
{
XCTSourceCodeLocation *xctloc = [[[XCTSourceCodeLocation alloc] initWithFilePath:[loc file] lineNumber:[loc line]] autorelease];
XCTSourceCodeContext *xctctx = [[[XCTSourceCodeContext alloc] initWithLocation:xctloc] autorelease];
XCTIssue *issue = [[[XCTIssue alloc] initWithType:XCTIssueTypeAssertionFailure compactDescription:description
detailedDescription:nil sourceCodeContext:xctctx associatedError:nil attachments:[NSArray array]] autorelease];
[testCase recordIssue:issue];
}
else
#endif
#endif
if((testCase != nil) && [testCase respondsToSelector:@selector(recordFailureWithDescription:inFile:atLine:expected:)])
{
[testCase recordFailureWithDescription:description inFile:[loc file] atLine:[loc line] expected:NO];
}
else if((testCase != nil) && [testCase respondsToSelector:@selector(failWithException:)])
{
NSException *exception = nil;
if([NSException instancesRespondToSelector:@selector(failureInFile:atLine:withDescription:)])
{
exception = [NSException failureInFile:[loc file] atLine:(int)[loc line] withDescription:description];
}
else
{
NSString *reason = [NSString stringWithFormat:@"%@:%lu %@", [loc file], (unsigned long)[loc line], description];
exception = [NSException exceptionWithName:@"OCMockTestFailure" reason:reason userInfo:nil];
}
[testCase failWithException:exception];
}
else if(loc != nil)
{
NSLog(@"%@:%lu %@", [loc file], (unsigned long)[loc line], description);
NSString *reason = [NSString stringWithFormat:@"%@:%lu %@", [loc file], (unsigned long)[loc line], description];
[[NSException exceptionWithName:@"OCMockTestFailure" reason:reason userInfo:nil] raise];
}
else
{
NSLog(@"%@", description);
[[NSException exceptionWithName:@"OCMockTestFailure" reason:description userInfo:nil] raise];
}
}
================================================
FILE: Pods/OCMock/Source/OCMock/OCMFunctionsPrivate.h
================================================
/*
* Copyright (c) 2014-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
@class OCMLocation;
@class OCClassMockObject;
@class OCPartialMockObject;
BOOL OCMIsClassType(const char *objCType);
BOOL OCMIsBlockType(const char *objCType);
BOOL OCMIsObjectType(const char *objCType);
BOOL OCMIsPointerType(const char *objCType);
const char *OCMTypeWithoutQualifiers(const char *objCType);
BOOL OCMEqualTypesAllowingOpaqueStructs(const char *type1, const char *type2);
CFNumberType OCMNumberTypeForObjCType(const char *objcType);
BOOL OCMIsNilValue(const char *objectCType, const void *value, size_t valueSize);
BOOL OCMIsAppleBaseClass(Class cls);
BOOL OCMIsApplePrivateMethod(Class cls, SEL sel);
Class OCMCreateSubclass(Class cls, void *ref);
BOOL OCMIsMockSubclass(Class cls);
void OCMDisposeSubclass(Class cls);
BOOL OCMIsAliasSelector(SEL selector);
SEL OCMAliasForOriginalSelector(SEL selector);
SEL OCMOriginalSelectorForAlias(SEL selector);
void OCMSetAssociatedMockForClass(OCClassMockObject *mock, Class aClass);
OCClassMockObject *OCMGetAssociatedMockForClass(Class aClass, BOOL includeSuperclasses);
void OCMSetAssociatedMockForObject(OCClassMockObject *mock, id anObject);
OCPartialMockObject *OCMGetAssociatedMockForObject(id anObject);
void OCMReportFailure(OCMLocation *loc, NSString *description);
BOOL OCMIsBlock(id potentialBlock);
BOOL OCMIsNonEscapingBlock(id block);
struct OCMBlockDef
{
void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
int flags;
int reserved;
void (*invoke)(void *, ...);
struct block_descriptor {
unsigned long int reserved; // NULL
unsigned long int size; // sizeof(struct Block_literal_1)
// optional helper functions
void (*copy_helper)(void *dst, void *src); // IFF (1<<25)
void (*dispose_helper)(void *src); // IFF (1<<25)
// required ABI.2010.3.16
const char *signature; // IFF (1<<30)
} *descriptor;
};
enum
{
OCMBlockIsNoEscape = (1 << 23),
OCMBlockDescriptionFlagsHasCopyDispose = (1 << 25),
OCMBlockDescriptionFlagsHasSignature = (1 << 30)
};
================================================
FILE: Pods/OCMock/Source/OCMock/OCMIndirectReturnValueProvider.h
================================================
/*
* Copyright (c) 2009-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
@interface OCMIndirectReturnValueProvider : NSObject
{
id provider;
SEL selector;
}
- (id)initWithProvider:(id)aProvider andSelector:(SEL)aSelector;
- (void)handleInvocation:(NSInvocation *)anInvocation;
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMIndirectReturnValueProvider.m
================================================
/*
* Copyright (c) 2009-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCMIndirectReturnValueProvider.h"
@implementation OCMIndirectReturnValueProvider
- (id)initWithProvider:(id)aProvider andSelector:(SEL)aSelector
{
if((self = [super init]))
{
provider = [aProvider retain];
selector = aSelector;
}
return self;
}
- (void)dealloc
{
[provider release];
[super dealloc];
}
- (void)handleInvocation:(NSInvocation *)anInvocation
{
id originalTarget = [anInvocation target];
SEL originalSelector = [anInvocation selector];
[anInvocation setTarget:provider];
[anInvocation setSelector:selector];
[anInvocation invoke];
[anInvocation setTarget:originalTarget];
[anInvocation setSelector:originalSelector];
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMInvocationExpectation.h
================================================
/*
* Copyright (c) 2014-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCMInvocationStub.h"
@interface OCMInvocationExpectation : OCMInvocationStub
{
BOOL matchAndReject;
BOOL isSatisfied;
}
- (void)setMatchAndReject:(BOOL)flag;
- (BOOL)isMatchAndReject;
- (BOOL)isSatisfied;
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMInvocationExpectation.m
================================================
/*
* Copyright (c) 2014-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "NSInvocation+OCMAdditions.h"
#import "OCMInvocationExpectation.h"
@implementation OCMInvocationExpectation
- (void)setMatchAndReject:(BOOL)flag
{
matchAndReject = flag;
if(matchAndReject)
isSatisfied = YES;
}
- (BOOL)isMatchAndReject
{
return matchAndReject;
}
- (BOOL)isSatisfied
{
return isSatisfied;
}
- (void)addInvocationAction:(id)anAction
{
if(matchAndReject)
{
[NSException raise:NSInternalInconsistencyException format:@"%@: cannot add action to a reject stub; got %@",
[self description], anAction];
}
[super addInvocationAction:anAction];
}
- (void)handleInvocation:(NSInvocation *)anInvocation
{
if(matchAndReject)
{
isSatisfied = NO;
[NSException raise:NSInternalInconsistencyException format:@"%@: explicitly disallowed method invoked: %@",
[self description], [anInvocation invocationDescription]];
}
else
{
[super handleInvocation:anInvocation];
isSatisfied = YES;
}
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMInvocationMatcher.h
================================================
/*
* Copyright (c) 2014-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
@interface OCMInvocationMatcher : NSObject
{
NSInvocation *recordedInvocation;
BOOL recordedAsClassMethod;
BOOL ignoreNonObjectArgs;
}
- (void)setInvocation:(NSInvocation *)anInvocation;
- (NSInvocation *)recordedInvocation;
- (void)setRecordedAsClassMethod:(BOOL)flag;
- (BOOL)recordedAsClassMethod;
- (void)setIgnoreNonObjectArgs:(BOOL)flag;
- (BOOL)matchesSelector:(SEL)aSelector;
- (BOOL)matchesInvocation:(NSInvocation *)anInvocation;
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMInvocationMatcher.m
================================================
/*
* Copyright (c) 2014-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
#import "NSInvocation+OCMAdditions.h"
#import "OCMInvocationMatcher.h"
#import "OCMArg.h"
#import "OCMConstraint.h"
#import "OCMFunctionsPrivate.h"
#import "OCMPassByRefSetter.h"
@interface NSObject (HCMatcherDummy)
- (BOOL)matches:(id)item;
@end
@implementation OCMInvocationMatcher
- (void)dealloc
{
[recordedInvocation release];
[super dealloc];
}
- (void)setInvocation:(NSInvocation *)anInvocation
{
[recordedInvocation release];
// Don't do a regular -retainArguments on the invocation that we use for matching. NSInvocation
// effectively does an strcpy on char* arguments which messes up matching them literally and blows
// up with anyPointer (in strlen since it's not actually a C string). Also on the off-chance that
// anInvocation contains self as an argument, -retainArguments would create a retain cycle.
[anInvocation retainObjectArgumentsExcludingObject:self];
recordedInvocation = [anInvocation retain];
}
- (void)setRecordedAsClassMethod:(BOOL)flag
{
recordedAsClassMethod = flag;
}
- (BOOL)recordedAsClassMethod
{
return recordedAsClassMethod;
}
- (void)setIgnoreNonObjectArgs:(BOOL)flag
{
ignoreNonObjectArgs = flag;
}
- (NSString *)description
{
return [recordedInvocation invocationDescription];
}
- (NSInvocation *)recordedInvocation
{
return recordedInvocation;
}
- (BOOL)matchesSelector:(SEL)sel
{
if(sel == [recordedInvocation selector])
return YES;
if(OCMIsAliasSelector(sel) && OCMOriginalSelectorForAlias(sel) == [recordedInvocation selector])
return YES;
return NO;
}
- (BOOL)matchesInvocation:(NSInvocation *)anInvocation
{
id target = [anInvocation target];
BOOL isClassMethodInvocation = (target != nil) && (target == [target class]);
if(isClassMethodInvocation != recordedAsClassMethod)
return NO;
if(![self matchesSelector:[anInvocation selector]])
return NO;
NSMethodSignature *signature = [recordedInvocation methodSignature];
NSUInteger n = [signature numberOfArguments];
for(NSUInteger i = 2; i < n; i++)
{
if(ignoreNonObjectArgs && !OCMIsObjectType([signature getArgumentTypeAtIndex:i]))
{
continue;
}
id recordedArg = [recordedInvocation getArgumentAtIndexAsObject:i];
id passedArg = [anInvocation getArgumentAtIndexAsObject:i];
if([recordedArg isProxy])
{
if(![recordedArg isEqual:passedArg])
return NO;
continue;
}
if([recordedArg isKindOfClass:[NSValue class]])
recordedArg = [OCMArg resolveSpecialValues:recordedArg];
if([recordedArg isKindOfClass:[OCMConstraint class]])
{
if([recordedArg evaluate:passedArg] == NO)
return NO;
}
else if([recordedArg isKindOfClass:[OCMArgAction class]])
{
// ignore, will be dealt with in handleInvocation: where applicable
}
else if([recordedArg conformsToProtocol:objc_getProtocol("HCMatcher")])
{
if([recordedArg matches:passedArg] == NO)
return NO;
}
else
{
if(([recordedArg class] == [NSNumber class]) && ([(NSNumber *)recordedArg compare:(NSNumber *)passedArg] != NSOrderedSame))
return NO;
if(([recordedArg isEqual:passedArg] == NO) && !((recordedArg == nil) && (passedArg == nil)))
return NO;
}
}
return YES;
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMInvocationStub.h
================================================
/*
* Copyright (c) 2014-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCMInvocationMatcher.h"
@interface OCMInvocationStub : OCMInvocationMatcher
{
NSMutableArray *invocationActions;
}
- (void)addInvocationAction:(id)anAction;
- (NSArray *)invocationActions;
- (void)handleInvocation:(NSInvocation *)anInvocation;
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMInvocationStub.m
================================================
/*
* Copyright (c) 2014-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "NSInvocation+OCMAdditions.h"
#import "OCMInvocationStub.h"
#import "OCMArg.h"
#import "OCMArgAction.h"
#define UNSET_RETURN_VALUE_MARKER ((id)0x01234567)
@implementation OCMInvocationStub
- (id)init
{
self = [super init];
invocationActions = [[NSMutableArray alloc] init];
return self;
}
- (void)dealloc
{
[invocationActions release];
[super dealloc];
}
- (void)addInvocationAction:(id)anAction
{
[invocationActions addObject:anAction];
}
- (NSArray *)invocationActions
{
return invocationActions;
}
- (void)handleInvocation:(NSInvocation *)anInvocation
{
[self invokeArgActionsForInvocation:anInvocation];
id target = [anInvocation target];
BOOL isInInitFamily = [anInvocation methodIsInInitFamily];
BOOL isInCreateFamily = isInInitFamily ? NO : [anInvocation methodIsInCreateFamily];
if(isInInitFamily || isInCreateFamily)
{
id returnVal = UNSET_RETURN_VALUE_MARKER;
[anInvocation setReturnValue:&returnVal];
[self invokeActionsForInvocation:anInvocation];
[anInvocation getReturnValue:&returnVal];
if(returnVal == UNSET_RETURN_VALUE_MARKER)
[NSException raise:NSInvalidArgumentException format:@"%@ was stubbed but no return value set. A return value is required for all alloc/copy/new/mutablecopy/init methods. If you intended to return nil, make this explicit with .andReturn(nil)", NSStringFromSelector([anInvocation selector])];
if(isInCreateFamily)
{
// methods that "create" an object return it with an extra retain count
[returnVal retain];
}
if(isInInitFamily)
{
// init family methods "consume" self and retain their return value. Do the retain
// first in case the return value and self are the same. The analyzer doesn't
// understand this; see #456 for details. In this case we also need to do something
// harmless with target or else the analyzer will complain about it not being used.
[returnVal retain];
#ifndef __clang_analyzer__
[target release];
#else
[target class];
#endif
}
}
else
{
[self invokeActionsForInvocation:anInvocation];
}
}
- (void)invokeArgActionsForInvocation:(NSInvocation *)anInvocation
{
NSMethodSignature *signature = [recordedInvocation methodSignature];
NSUInteger n = [signature numberOfArguments];
for(NSUInteger i = 2; i < n; i++)
{
id recordedArg = [recordedInvocation getArgumentAtIndexAsObject:i];
id passedArg = [anInvocation getArgumentAtIndexAsObject:i];
if([recordedArg isProxy])
continue;
if([recordedArg isKindOfClass:[NSValue class]])
recordedArg = [OCMArg resolveSpecialValues:recordedArg];
if([recordedArg isKindOfClass:[OCMArgAction class]])
[recordedArg handleArgument:passedArg];
}
}
- (void)invokeActionsForInvocation:(NSInvocation *)anInvocation
{
[invocationActions makeObjectsPerformSelector:@selector(handleInvocation:) withObject:anInvocation];
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMLocation.h
================================================
/*
* Copyright (c) 2014-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
#import
@interface OCMLocation : NSObject
{
id testCase;
NSString *file;
NSUInteger line;
}
+ (instancetype)locationWithTestCase:(id)aTestCase file:(NSString *)aFile line:(NSUInteger)aLine;
- (instancetype)initWithTestCase:(id)aTestCase file:(NSString *)aFile line:(NSUInteger)aLine;
- (id)testCase;
- (NSString *)file;
- (NSUInteger)line;
@end
OCMOCK_EXTERN OCMLocation *OCMMakeLocation(id testCase, const char *file, int line);
================================================
FILE: Pods/OCMock/Source/OCMock/OCMLocation.m
================================================
/*
* Copyright (c) 2014-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCMLocation.h"
@implementation OCMLocation
+ (instancetype)locationWithTestCase:(id)aTestCase file:(NSString *)aFile line:(NSUInteger)aLine
{
return [[[OCMLocation alloc] initWithTestCase:aTestCase file:aFile line:aLine] autorelease];
}
- (instancetype)initWithTestCase:(id)aTestCase file:(NSString *)aFile line:(NSUInteger)aLine
{
if((self = [super init]))
{
testCase = aTestCase;
file = [aFile retain];
line = aLine;
}
return self;
}
- (void)dealloc
{
[file release];
[super dealloc];
}
- (id)testCase
{
return testCase;
}
- (NSString *)file
{
return file;
}
- (NSUInteger)line
{
return line;
}
@end
OCMLocation *OCMMakeLocation(id testCase, const char *fileCString, int line)
{
return [OCMLocation locationWithTestCase:testCase file:[NSString stringWithUTF8String:fileCString] line:line];
}
================================================
FILE: Pods/OCMock/Source/OCMock/OCMMacroState.h
================================================
/*
* Copyright (c) 2014-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
@class OCMLocation;
@class OCMQuantifier;
@class OCMRecorder;
@class OCMStubRecorder;
@class OCMockObject;
@interface OCMMacroState : NSObject
{
OCMRecorder *recorder;
BOOL invocationDidThrow;
}
+ (void)beginStubMacro;
+ (OCMStubRecorder *)endStubMacro;
+ (void)beginExpectMacro;
+ (OCMStubRecorder *)endExpectMacro;
+ (void)beginRejectMacro;
+ (OCMStubRecorder *)endRejectMacro;
+ (void)beginVerifyMacroAtLocation:(OCMLocation *)aLocation;
+ (void)beginVerifyMacroAtLocation:(OCMLocation *)aLocation withQuantifier:(OCMQuantifier *)quantifier;
+ (void)endVerifyMacro;
+ (OCMMacroState *)globalState;
- (void)setRecorder:(OCMRecorder *)aRecorder;
- (OCMRecorder *)recorder;
- (void)switchToClassMethod;
- (void)setInvocationDidThrow:(BOOL)flag;
- (BOOL)invocationDidThrow;
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMMacroState.m
================================================
/*
* Copyright (c) 2014-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCMMacroState.h"
#import "OCMExpectationRecorder.h"
#import "OCMVerifier.h"
@implementation OCMMacroState
static NSString *const OCMGlobalStateKey = @"OCMGlobalStateKey";
#pragma mark Methods to begin/end macros
+ (void)beginStubMacro
{
OCMStubRecorder *recorder = [[[OCMStubRecorder alloc] init] autorelease];
OCMMacroState *macroState = [[OCMMacroState alloc] initWithRecorder:recorder];
[NSThread currentThread].threadDictionary[OCMGlobalStateKey] = macroState;
[macroState release];
}
+ (OCMStubRecorder *)endStubMacro
{
NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary;
OCMMacroState *globalState = threadDictionary[OCMGlobalStateKey];
OCMStubRecorder *recorder = [[(OCMStubRecorder *)[globalState recorder] retain] autorelease];
BOOL didThrow = [globalState invocationDidThrow];
[threadDictionary removeObjectForKey:OCMGlobalStateKey];
if(didThrow == NO && [recorder didRecordInvocation] == NO)
{
[NSException raise:NSInternalInconsistencyException
format:@"Did not record an invocation in OCMStub/OCMExpect/OCMReject.\n"
@"Possible causes are:\n"
@"- The receiver is not a mock object.\n"
@"- The selector conflicts with a selector implemented by OCMStubRecorder/OCMExpectationRecorder."];
}
return recorder;
}
+ (void)beginExpectMacro
{
OCMExpectationRecorder *recorder = [[[OCMExpectationRecorder alloc] init] autorelease];
OCMMacroState *macroState = [[OCMMacroState alloc] initWithRecorder:recorder];
[NSThread currentThread].threadDictionary[OCMGlobalStateKey] = macroState;
[macroState release];
}
+ (OCMStubRecorder *)endExpectMacro
{
return [self endStubMacro];
}
+ (void)beginRejectMacro
{
OCMExpectationRecorder *recorder = [[[OCMExpectationRecorder alloc] init] autorelease];
OCMMacroState *macroState = [[OCMMacroState alloc] initWithRecorder:recorder];
[NSThread currentThread].threadDictionary[OCMGlobalStateKey] = macroState;
[macroState release];
}
+ (OCMStubRecorder *)endRejectMacro
{
OCMMacroState *globalState = [NSThread currentThread].threadDictionary[OCMGlobalStateKey];
// Calling never after the invocation to avoid running afoul of ARC's expectations on
// return values from init methods.
[(OCMExpectationRecorder *)[globalState recorder] never];
return [self endStubMacro];
}
+ (void)beginVerifyMacroAtLocation:(OCMLocation *)aLocation
{
return [self beginVerifyMacroAtLocation:aLocation withQuantifier:nil];
}
+ (void)beginVerifyMacroAtLocation:(OCMLocation *)aLocation withQuantifier:(OCMQuantifier *)quantifier
{
OCMVerifier *recorder = [[[OCMVerifier alloc] init] autorelease];
[recorder setLocation:aLocation];
[recorder setQuantifier:quantifier];
OCMMacroState *macroState = [[OCMMacroState alloc] initWithRecorder:recorder];
[NSThread currentThread].threadDictionary[OCMGlobalStateKey] = macroState;
[macroState release];
}
+ (void)endVerifyMacro
{
NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary;
OCMMacroState *globalState = threadDictionary[OCMGlobalStateKey];
OCMVerifier *verifier = [[(OCMVerifier *)[globalState recorder] retain] autorelease];
BOOL didThrow = [globalState invocationDidThrow];
[threadDictionary removeObjectForKey:OCMGlobalStateKey];
if(didThrow == NO && [verifier didRecordInvocation] == NO)
{
[NSException raise:NSInternalInconsistencyException
format:@"Did not record an invocation in OCMVerify.\n"
@"Possible causes are:\n"
@"- The receiver is not a mock object.\n"
@"- The selector conflicts with a selector implemented by OCMVerifier."];
}
}
#pragma mark Accessing global state
+ (OCMMacroState *)globalState
{
return [NSThread currentThread].threadDictionary[OCMGlobalStateKey];
}
#pragma mark Init, dealloc, accessors
- (id)initWithRecorder:(OCMRecorder *)aRecorder
{
if((self = [super init]))
{
recorder = [aRecorder retain];
}
return self;
}
- (void)dealloc
{
[recorder release];
if([NSThread currentThread].threadDictionary[OCMGlobalStateKey] == self)
[NSException raise:NSInternalInconsistencyException format:@"Unexpected dealloc while set as the global state"];
[super dealloc];
}
- (void)setRecorder:(OCMRecorder *)aRecorder
{
[recorder autorelease];
recorder = [aRecorder retain];
}
- (OCMRecorder *)recorder
{
return recorder;
}
- (void)setInvocationDidThrow:(BOOL)flag
{
invocationDidThrow = flag;
}
- (BOOL)invocationDidThrow
{
return invocationDidThrow;
}
#pragma mark Changing the recorder
- (void)switchToClassMethod
{
[recorder classMethod];
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMNonRetainingObjectReturnValueProvider.h
================================================
/*
* Copyright (c) 2019-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
@interface OCMNonRetainingObjectReturnValueProvider : NSObject
{
id returnValue;
}
- (instancetype)initWithValue:(id)aValue;
- (void)handleInvocation:(NSInvocation *)anInvocation;
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMNonRetainingObjectReturnValueProvider.m
================================================
/*
* Copyright (c) 2019-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "NSInvocation+OCMAdditions.h"
#import "OCMNonRetainingObjectReturnValueProvider.h"
#import "OCMFunctions.h"
@implementation OCMNonRetainingObjectReturnValueProvider
- (instancetype)initWithValue:(id)aValue
{
if((self = [super init]))
returnValue = aValue;
return self;
}
- (void)handleInvocation:(NSInvocation *)anInvocation
{
if(!OCMIsObjectType([[anInvocation methodSignature] methodReturnType]))
{
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Expected invocation with object return type. Did you mean to use andReturnValue: instead?" userInfo:nil];
}
[anInvocation setReturnValue:&returnValue];
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMNotificationPoster.h
================================================
/*
* Copyright (c) 2009-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
@interface OCMNotificationPoster : NSObject
{
NSNotification *notification;
}
- (id)initWithNotification:(id)aNotification;
- (void)handleInvocation:(NSInvocation *)anInvocation;
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMNotificationPoster.m
================================================
/*
* Copyright (c) 2009-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCMNotificationPoster.h"
@implementation OCMNotificationPoster
- (id)initWithNotification:(id)aNotification
{
if((self = [super init]))
{
notification = [aNotification retain];
}
return self;
}
- (void)dealloc
{
[notification release];
[super dealloc];
}
- (void)handleInvocation:(NSInvocation *)anInvocation
{
[[NSNotificationCenter defaultCenter] postNotification:notification];
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMObjectReturnValueProvider.h
================================================
/*
* Copyright (c) 2009-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCMNonRetainingObjectReturnValueProvider.h"
@interface OCMObjectReturnValueProvider : OCMNonRetainingObjectReturnValueProvider
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMObjectReturnValueProvider.m
================================================
/*
* Copyright (c) 2009-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCMObjectReturnValueProvider.h"
@implementation OCMObjectReturnValueProvider
- (instancetype)initWithValue:(id)aValue
{
if((self = [super initWithValue:aValue]))
[returnValue retain];
return self;
}
- (void)dealloc
{
[returnValue release];
[super dealloc];
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMObserverRecorder.h
================================================
/*
* Copyright (c) 2009-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
@interface OCMObserverRecorder : NSObject
{
NSNotification *recordedNotification;
}
- (NSNotification *)notificationWithName:(NSString *)name object:(id)sender;
- (NSNotification *)notificationWithName:(NSString *)name object:(id)sender userInfo:(NSDictionary *)userInfo;
- (BOOL)matchesNotification:(NSNotification *)aNotification;
- (BOOL)argument:(id)expectedArg matchesArgument:(id)observedArg;
- (BOOL)didRecordInvocation __used;
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMObserverRecorder.m
================================================
/*
* Copyright (c) 2009-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
#import "OCMObserverRecorder.h"
#import "OCMConstraint.h"
@interface NSObject (HCMatcherDummy)
- (BOOL)matches:(id)item;
@end
#pragma mark -
@implementation OCMObserverRecorder
#pragma mark Initialisers, description, accessors, etc.
- (void)dealloc
{
[recordedNotification release];
[super dealloc];
}
- (BOOL)didRecordInvocation
{
return YES; // Needed for macro use, and recorder can only end up in macro state if it was used.
}
#pragma mark Recording
- (NSNotification *)notificationWithName:(NSString *)name object:(id)sender
{
recordedNotification = [[NSNotification notificationWithName:name object:sender] retain];
return nil;
}
- (NSNotification *)notificationWithName:(NSString *)name object:(id)sender userInfo:(NSDictionary *)userInfo
{
recordedNotification = [[NSNotification notificationWithName:name object:sender userInfo:userInfo] retain];
return nil;
}
#pragma mark Verification
- (BOOL)matchesNotification:(NSNotification *)aNotification
{
return [self argument:[recordedNotification name] matchesArgument:[aNotification name]] &&
[self argument:[recordedNotification object] matchesArgument:[aNotification object]] &&
[self argument:[recordedNotification userInfo] matchesArgument:[aNotification userInfo]];
}
- (BOOL)argument:(id)expectedArg matchesArgument:(id)observedArg
{
if([expectedArg isKindOfClass:[OCMConstraint class]])
{
return [expectedArg evaluate:observedArg];
}
else if([expectedArg conformsToProtocol:objc_getProtocol("HCMatcher")])
{
return [expectedArg matches:observedArg];
}
else if(expectedArg == observedArg)
{
return YES;
}
else if(expectedArg == nil || observedArg == nil)
{
return NO;
}
else
{
return [expectedArg isEqual:observedArg];
}
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMPassByRefSetter.h
================================================
/*
* Copyright (c) 2009-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCMArgAction.h"
@interface OCMPassByRefSetter : OCMArgAction
{
id value;
}
- (id)initWithValue:(id)value;
+ (BOOL)isPassByRefSetterInstance:(void *)ptr;
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMPassByRefSetter.m
================================================
/*
* Copyright (c) 2009-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCMPassByRefSetter.h"
@implementation OCMPassByRefSetter
// Stores a reference to all OCMPassByRefSetter instances so that OCMArg can
// check for any given pointer whether its an OCMPassByRefSetter without having
// to get the class for the pointer (see #503). The pointers are stored without
// reference count.
static NSHashTable *_OCMPassByRefSetterInstances = NULL;
+ (void)initialize
{
if(self == [OCMPassByRefSetter class])
{
_OCMPassByRefSetterInstances = [[NSHashTable hashTableWithOptions:NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality] retain];
}
}
+ (BOOL)isPassByRefSetterInstance:(void *)ptr
{
@synchronized(_OCMPassByRefSetterInstances)
{
return NSHashGet(_OCMPassByRefSetterInstances, ptr) != NULL;
}
}
- (id)initWithValue:(id)aValue
{
if((self = [super init]))
{
value = [aValue retain];
@synchronized(_OCMPassByRefSetterInstances)
{
NSHashInsertKnownAbsent(_OCMPassByRefSetterInstances, self);
}
}
return self;
}
- (void)dealloc
{
[value release];
@synchronized(_OCMPassByRefSetterInstances)
{
NSHashRemove(_OCMPassByRefSetterInstances, self);
}
[super dealloc];
}
- (void)handleArgument:(id)arg
{
void *pointerValue = [arg pointerValue];
if(pointerValue != NULL)
{
if([value isKindOfClass:[NSValue class]])
[(NSValue *)value getValue:pointerValue];
else
*(id *)pointerValue = value;
}
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMQuantifier.h
================================================
/*
* Copyright (c) 2016-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
@interface OCMQuantifier : NSObject
{
NSUInteger expectedCount;
}
+ (instancetype)never;
+ (instancetype)exactly:(NSUInteger)count;
+ (instancetype)atLeast:(NSUInteger)count;
+ (instancetype)atMost:(NSUInteger)count;
- (BOOL)isValidCount:(NSUInteger)count;
- (NSString *)description;
@end
#define OCMNever() ([OCMQuantifier never])
#define OCMTimes(n) ([OCMQuantifier exactly:(n)])
#define OCMAtLeast(n) ([OCMQuantifier atLeast:(n)])
#define OCMAtMost(n) ([OCMQuantifier atMost:(n)])
#ifndef OCM_DISABLE_SHORT_QSYNTAX
#define never() OCMNever()
#define times(n) OCMTimes(n)
#define atLeast(n) OCMAtLeast(n)
#define atMost(n) OCMAtMost(n)
#endif
================================================
FILE: Pods/OCMock/Source/OCMock/OCMQuantifier.m
================================================
/*
* Copyright (c) 2016-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCMQuantifier.h"
#import "OCMMacroState.h"
#import "OCMVerifier.h"
@interface OCMExactCountQuantifier : OCMQuantifier
@end
@interface OCMAtLeastQuantifier : OCMQuantifier
@end
@interface OCMAtMostQuantifier : OCMQuantifier
@end
@implementation OCMQuantifier
+ (instancetype)exactly:(NSUInteger)count
{
return [[[OCMExactCountQuantifier alloc] initWithCount:count] autorelease];
}
+ (instancetype)never
{
return [self exactly:0];
}
+ (instancetype)atLeast:(NSUInteger)count
{
return [[[OCMAtLeastQuantifier alloc] initWithCount:count] autorelease];
}
+ (instancetype)atMost:(NSUInteger)count
{
return [[[OCMAtMostQuantifier alloc] initWithCount:count] autorelease];
}
- (instancetype)initWithCount:(NSUInteger)count
{
if((self = [super init]) != nil)
{
expectedCount = count;
[(OCMVerifier *)[[OCMMacroState globalState] recorder] setQuantifier:self];
}
return self;
}
- (BOOL)isValidCount:(NSUInteger)count
{
return NO;
}
- (NSString *)description
{
switch(expectedCount)
{
case 0: return @"never";
case 1: return @"once";
default: return [NSString stringWithFormat:@"%lu times", (unsigned long)expectedCount];
}
}
@end
@implementation OCMExactCountQuantifier
- (BOOL)isValidCount:(NSUInteger)count
{
return count == expectedCount;
}
@end
@implementation OCMAtLeastQuantifier
- (instancetype)initWithCount:(NSUInteger)count
{
if(count == 0)
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Count for an at-least quantifier cannot be zero." userInfo:nil];
return [super initWithCount:count];
}
- (BOOL)isValidCount:(NSUInteger)count
{
return count >= expectedCount;
}
- (NSString *)description
{
return [@"at least " stringByAppendingString:[super description]];
}
@end
@implementation OCMAtMostQuantifier
- (instancetype)initWithCount:(NSUInteger)count
{
if(count == 0)
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Count for an at-most quantifier cannot be zero. Use never or exactly-zero quantifier instead." userInfo:nil];
return [super initWithCount:count];
}
- (BOOL)isValidCount:(NSUInteger)count
{
return count <= expectedCount;
}
- (NSString *)description
{
return [@"at most " stringByAppendingString:[super description]];
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMRealObjectForwarder.h
================================================
/*
* Copyright (c) 2010-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
@interface OCMRealObjectForwarder : NSObject
{
}
- (void)handleInvocation:(NSInvocation *)anInvocation;
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMRealObjectForwarder.m
================================================
/*
* Copyright (c) 2010-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
#import "NSInvocation+OCMAdditions.h"
#import "OCMRealObjectForwarder.h"
#import "OCMFunctionsPrivate.h"
#import "OCPartialMockObject.h"
@implementation OCMRealObjectForwarder
- (void)handleInvocation:(NSInvocation *)anInvocation
{
id invocationTarget = [anInvocation target];
BOOL isInInitFamily = [anInvocation methodIsInInitFamily];
BOOL isInCreateFamily = isInInitFamily ? NO : [anInvocation methodIsInCreateFamily];
[anInvocation setSelector:OCMAliasForOriginalSelector([anInvocation selector])];
if([invocationTarget isProxy])
{
if(!class_getInstanceMethod([invocationTarget mockObjectClass], @selector(realObject)))
[NSException raise:NSInternalInconsistencyException format:@"Method andForwardToRealObject can only be used with partial mocks and class methods."];
NSObject *realObject = [(OCPartialMockObject *)invocationTarget realObject];
[anInvocation setTarget:realObject];
if(isInInitFamily)
{
// The init method of the real object will "consume" self, but because the method was
// invoked on the mock and not the real object a corresponding retain is missing; so
// we do this here. The analyzer doesn't understand this; see #456 for details.
#ifndef __clang_analyzer__
[realObject retain];
#endif
}
}
[anInvocation invoke];
if(isInInitFamily || isInCreateFamily)
{
// After invoking the method on the real object the return value's retain count is correct,
// but because we have a chain of handlers for an invocation and we handle the retain count
// adjustments at the end in the stub, we undo the additional retains here.
id returnVal;
[anInvocation getReturnValue:&returnVal];
[returnVal autorelease];
}
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMRecorder.h
================================================
/*
* Copyright (c) 2014-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
@class OCMockObject;
@class OCMInvocationMatcher;
@interface OCMRecorder : NSProxy
{
OCMockObject *mockObject;
OCMInvocationMatcher *invocationMatcher;
BOOL didRecordInvocation;
BOOL shouldReturnMockFromInit;
}
- (instancetype)init;
- (instancetype)initWithMockObject:(OCMockObject *)aMockObject;
- (void)setMockObject:(OCMockObject *)aMockObject;
- (void)setShouldReturnMockFromInit:(BOOL)flag;
- (OCMInvocationMatcher *)invocationMatcher;
- (BOOL)didRecordInvocation;
- (id)classMethod;
- (id)ignoringNonObjectArgs;
@end
@interface OCMRecorder (Properties)
#define ignoringNonObjectArgs() _ignoringNonObjectArgs()
@property(nonatomic, readonly) OCMRecorder * (^_ignoringNonObjectArgs)(void);
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMRecorder.m
================================================
/*
* Copyright (c) 2014-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
#import
#import "NSInvocation+OCMAdditions.h"
#import "OCClassMockObject.h"
#import "OCMInvocationMatcher.h"
#import "OCMRecorder.h"
@implementation OCMRecorder
- (instancetype)init
{
// no super, we're inheriting from NSProxy
didRecordInvocation = NO;
shouldReturnMockFromInit = NO;
return self;
}
- (instancetype)initWithMockObject:(OCMockObject *)aMockObject
{
[self init];
[self setMockObject:aMockObject];
return self;
}
- (void)setMockObject:(OCMockObject *)aMockObject
{
mockObject = aMockObject;
}
- (void)setShouldReturnMockFromInit:(BOOL)flag
{
shouldReturnMockFromInit = flag;
}
- (void)dealloc
{
[invocationMatcher release];
[super dealloc];
}
- (NSString *)description
{
return [invocationMatcher description];
}
- (OCMInvocationMatcher *)invocationMatcher
{
return invocationMatcher;
}
- (BOOL)didRecordInvocation
{
return didRecordInvocation;
}
#pragma mark Modifying the matcher
- (id)classMethod
{
// should we handle the case where this is called with a mock that isn't a class mock?
[invocationMatcher setRecordedAsClassMethod:YES];
return self;
}
- (id)ignoringNonObjectArgs
{
[invocationMatcher setIgnoreNonObjectArgs:YES];
return self;
}
#pragma mark Recording the actual invocation
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if([invocationMatcher recordedAsClassMethod])
return [[(OCClassMockObject *)mockObject mockedClass] methodSignatureForSelector:aSelector];
NSMethodSignature *signature = [mockObject methodSignatureForSelector:aSelector];
if(signature == nil)
{
// if we're a working with a class mock and there is a class method, auto-switch
if(([object_getClass(mockObject) isSubclassOfClass:[OCClassMockObject class]]) &&
([[(OCClassMockObject *)mockObject mockedClass] respondsToSelector:aSelector]))
{
[self classMethod];
signature = [self methodSignatureForSelector:aSelector];
}
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
[anInvocation setTarget:nil];
didRecordInvocation = YES;
[invocationMatcher setInvocation:anInvocation];
// Code with ARC may retain the receiver of an init method before invoking it. In that case it
// relies on the init method returning an object it can release. So, we must set the correct
// return value here. Normally, the correct return value is the recorder but sometimes it's the
// mock. The decision is easier to make in the mock, which is why the mock sets a flag in the
// recorder and we simply use the flag here.
if([anInvocation methodIsInInitFamily])
{
id returnValue = shouldReturnMockFromInit ? (id)mockObject : (id)self;
[anInvocation setReturnValue:&returnValue];
}
}
- (void)doesNotRecognizeSelector:(SEL)aSelector __used
{
[NSException raise:NSInvalidArgumentException format:@"%@: cannot stub/expect/verify method '%@' because no such method exists in the mocked class.", mockObject, NSStringFromSelector(aSelector)];
}
@end
@implementation OCMRecorder (Properties)
@dynamic _ignoringNonObjectArgs;
- (OCMRecorder *(^)(void))_ignoringNonObjectArgs
{
id (^theBlock)(void) = ^(void) {
return [self ignoringNonObjectArgs];
};
return [[theBlock copy] autorelease];
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMStubRecorder.h
================================================
/*
* Copyright (c) 2004-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
#import
#import
#if !defined(OCM_DISABLE_XCTEST_FEATURES)
@class XCTestExpectation;
#endif
@interface OCMStubRecorder : OCMRecorder
- (id)andReturn:(id)anObject;
- (id)andReturnValue:(NSValue *)aValue;
- (id)andThrow:(NSException *)anException;
- (id)andPost:(NSNotification *)aNotification;
- (id)andCall:(SEL)selector onObject:(id)anObject;
- (id)andDo:(void (^)(NSInvocation *invocation))block;
- (id)andForwardToRealObject;
#if !defined(OCM_DISABLE_XCTEST_FEATURES)
- (id)andFulfill:(XCTestExpectation *)expectation;
#endif
@end
@interface OCMStubRecorder (Properties)
#define andReturn(aValue) _andReturn(({ \
__typeof__(aValue) _val = (aValue); \
NSValue *_nsval = [NSValue value:&_val withObjCType:@encode(__typeof__(_val))]; \
if (OCMIsObjectType(@encode(__typeof(_val)))) { \
objc_setAssociatedObject(_nsval, "OCMAssociatedBoxedValue", *(__unsafe_unretained id *) (void *) &_val, OBJC_ASSOCIATION_RETAIN); \
} \
_nsval; \
}))
@property (nonatomic, readonly) OCMStubRecorder *(^ _andReturn)(NSValue *);
#define andThrow(anException) _andThrow(anException)
@property (nonatomic, readonly) OCMStubRecorder *(^ _andThrow)(NSException *);
#define andPost(aNotification) _andPost(aNotification)
@property (nonatomic, readonly) OCMStubRecorder *(^ _andPost)(NSNotification *);
#define andCall(anObject, aSelector) _andCall(anObject, aSelector)
@property (nonatomic, readonly) OCMStubRecorder *(^ _andCall)(id, SEL);
#define andDo(aBlock) _andDo(aBlock)
@property (nonatomic, readonly) OCMStubRecorder *(^ _andDo)(void (^)(NSInvocation *));
#define andForwardToRealObject() _andForwardToRealObject()
@property (nonatomic, readonly) OCMStubRecorder *(^ _andForwardToRealObject)(void);
#if !defined(OCM_DISABLE_XCTEST_FEATURES)
#define andFulfill(anExpectation) _andFulfill(anExpectation)
@property (nonatomic, readonly) OCMStubRecorder *(^ _andFulfill)(XCTestExpectation *);
#endif
@property (nonatomic, readonly) OCMStubRecorder *(^ _ignoringNonObjectArgs)(void);
#define andBreak() _andDo(^(NSInvocation *_invocation) \
{ \
__builtin_debugtrap(); \
}) \
#define andLog(_format, ...) _andDo(^(NSInvocation *_invocation) \
{ \
NSLog(_format, ##__VA_ARGS__); \
}) \
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMStubRecorder.m
================================================
/*
* Copyright (c) 2004-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCMStubRecorder.h"
#import "OCClassMockObject.h"
#import "OCMBlockCaller.h"
#import "OCMBoxedReturnValueProvider.h"
#import "OCMExceptionReturnValueProvider.h"
#import "OCMIndirectReturnValueProvider.h"
#import "OCMInvocationStub.h"
#import "OCMNotificationPoster.h"
#import "OCMRealObjectForwarder.h"
#if !defined(OCM_DISABLE_XCTEST_FEATURES)
#import
#endif
@implementation OCMStubRecorder
#pragma mark Initialisers, description, accessors, etc.
- (id)init
{
if(invocationMatcher != nil)
[NSException raise:NSInternalInconsistencyException format:@"** Method init invoked twice on stub recorder. Are you trying to mock the init method? This is currently not supported."];
self = [super init];
invocationMatcher = [[OCMInvocationStub alloc] init];
return self;
}
- (OCMInvocationStub *)stub
{
return (OCMInvocationStub *)invocationMatcher;
}
#pragma mark Recording invocation actions
- (id)andReturn:(id)anObject
{
id action;
if(anObject == mockObject)
{
action = [[[OCMNonRetainingObjectReturnValueProvider alloc] initWithValue:anObject] autorelease];
}
else
{
action = [[[OCMObjectReturnValueProvider alloc] initWithValue:anObject] autorelease];
}
[[self stub] addInvocationAction:action];
return self;
}
- (id)andReturnValue:(NSValue *)aValue
{
[[self stub] addInvocationAction:[[[OCMBoxedReturnValueProvider alloc] initWithValue:aValue] autorelease]];
return self;
}
- (id)andThrow:(NSException *)anException
{
[[self stub] addInvocationAction:[[[OCMExceptionReturnValueProvider alloc] initWithValue:anException] autorelease]];
return self;
}
- (id)andPost:(NSNotification *)aNotification
{
[[self stub] addInvocationAction:[[[OCMNotificationPoster alloc] initWithNotification:aNotification] autorelease]];
return self;
}
- (id)andCall:(SEL)selector onObject:(id)anObject
{
[[self stub] addInvocationAction:[[[OCMIndirectReturnValueProvider alloc] initWithProvider:anObject andSelector:selector] autorelease]];
return self;
}
- (id)andDo:(void (^)(NSInvocation *))aBlock
{
[[self stub] addInvocationAction:[[[OCMBlockCaller alloc] initWithCallBlock:aBlock] autorelease]];
return self;
}
- (id)andForwardToRealObject
{
[[self stub] addInvocationAction:[[[OCMRealObjectForwarder alloc] init] autorelease]];
return self;
}
#if !defined(OCM_DISABLE_XCTEST_FEATURES)
- (id)andFulfill:(XCTestExpectation *)expectation
{
return [self andDo:^(NSInvocation *invocation) {
[expectation fulfill];
}];
}
#endif
#pragma mark Finishing recording
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
[super forwardInvocation:anInvocation];
[mockObject addStub:[self stub]];
}
@end
@implementation OCMStubRecorder (Properties)
@dynamic _andReturn;
- (OCMStubRecorder * (^)(NSValue *))_andReturn
{
id (^theBlock)(id) = ^(NSValue *aValue) {
if(OCMIsObjectType([aValue objCType]))
{
id objValue = nil;
[aValue getValue:&objValue]; // TODO: deprecated but replacement available in 10.13 only
return [self andReturn:objValue];
}
else
{
return [self andReturnValue:aValue];
}
};
return (id)[[theBlock copy] autorelease];
}
@dynamic _andThrow;
- (OCMStubRecorder * (^)(NSException *))_andThrow
{
id (^theBlock)(id) = ^(NSException *anException) {
return [self andThrow:anException];
};
return (id)[[theBlock copy] autorelease];
}
@dynamic _andPost;
- (OCMStubRecorder * (^)(NSNotification *))_andPost
{
id (^theBlock)(id) = ^(NSNotification *aNotification) {
return [self andPost:aNotification];
};
return (id)[[theBlock copy] autorelease];
}
@dynamic _andCall;
- (OCMStubRecorder * (^)(id, SEL))_andCall
{
id (^theBlock)(id, SEL) = ^(id anObject, SEL aSelector) {
return [self andCall:aSelector onObject:anObject];
};
return (id)[[theBlock copy] autorelease];
}
@dynamic _andDo;
- (OCMStubRecorder * (^)(void (^)(NSInvocation *)))_andDo
{
id (^theBlock)(void (^)(NSInvocation *)) = ^(void (^blockToCall)(NSInvocation *)) {
return [self andDo:blockToCall];
};
return (id)[[theBlock copy] autorelease];
}
@dynamic _andForwardToRealObject;
- (OCMStubRecorder * (^)(void))_andForwardToRealObject
{
id (^theBlock)(void) = ^(void) {
return [self andForwardToRealObject];
};
return (id)[[theBlock copy] autorelease];
}
#if !defined(OCM_DISABLE_XCTEST_FEATURES)
@dynamic _andFulfill;
- (OCMStubRecorder * (^)(XCTestExpectation *))_andFulfill
{
id (^theBlock)(XCTestExpectation *) = ^(XCTestExpectation *expectation) {
return [self andFulfill:expectation];
};
return (id)[[theBlock copy] autorelease];
}
#endif
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMVerifier.h
================================================
/*
* Copyright (c) 2014-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
@class OCMLocation;
@class OCMQuantifier;
@interface OCMVerifier : OCMRecorder
@property(strong) OCMLocation *location;
@property(strong) OCMQuantifier *quantifier;
- (id)withQuantifier:(OCMQuantifier *)quantifier;
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMVerifier.m
================================================
/*
* Copyright (c) 2014-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCMVerifier.h"
#import "OCMInvocationMatcher.h"
#import "OCMLocation.h"
#import "OCMQuantifier.h"
#import "OCMockObject.h"
@implementation OCMVerifier
- (id)init
{
if(invocationMatcher != nil)
[NSException raise:NSInternalInconsistencyException format:@"** Method init invoked twice on verifier. Are you trying to verify the init method? This is currently not supported."];
if((self = [super init]))
{
invocationMatcher = [[OCMInvocationMatcher alloc] init];
}
return self;
}
- (id)withQuantifier:(OCMQuantifier *)quantifier
{
[self setQuantifier:quantifier];
return self;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
[super forwardInvocation:anInvocation];
[mockObject verifyInvocation:invocationMatcher withQuantifier:self.quantifier atLocation:self.location];
}
- (void)dealloc
{
[_location release];
[_quantifier release];
[super dealloc];
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMock.h
================================================
/*
* Copyright (c) 2004-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
#import
================================================
FILE: Pods/OCMock/Source/OCMock/OCMockMacros.h
================================================
/*
* Copyright (c) 2014-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#ifdef OCM_DISABLE_SHORT_SYNTAX
#define OCM_DISABLE_SHORT_QSYNTAX
#endif
#define OCMClassMock(cls) [OCMockObject niceMockForClass:cls]
#define OCMStrictClassMock(cls) [OCMockObject mockForClass:cls]
#define OCMProtocolMock(protocol) [OCMockObject niceMockForProtocol:protocol]
#define OCMStrictProtocolMock(protocol) [OCMockObject mockForProtocol:protocol]
#define OCMPartialMock(obj) [OCMockObject partialMockForObject:obj]
#define OCMObserverMock() [OCMockObject observerMock]
#define OCMStub(invocation) \
({ \
_OCMSilenceWarnings( \
[OCMMacroState beginStubMacro]; \
OCMStubRecorder *recorder = nil; \
@try{ \
invocation; \
}@catch(...){ \
[[OCMMacroState globalState] setInvocationDidThrow:YES]; \
/* NOLINTNEXTLINE(google-objc-avoid-throwing-exception) */ \
@throw; \
}@finally{ \
recorder = [OCMMacroState endStubMacro]; \
} \
recorder; \
); \
})
#define OCMExpect(invocation) \
({ \
_OCMSilenceWarnings( \
[OCMMacroState beginExpectMacro]; \
OCMStubRecorder *recorder = nil; \
@try{ \
invocation; \
}@catch(...){ \
[[OCMMacroState globalState] setInvocationDidThrow:YES]; \
/* NOLINTNEXTLINE(google-objc-avoid-throwing-exception) */ \
@throw; \
}@finally{ \
recorder = [OCMMacroState endExpectMacro]; \
} \
recorder; \
); \
})
#define OCMReject(invocation) \
({ \
_OCMSilenceWarnings( \
[OCMMacroState beginRejectMacro]; \
OCMStubRecorder *recorder = nil; \
@try{ \
invocation; \
}@catch(...){ \
[[OCMMacroState globalState] setInvocationDidThrow:YES]; \
/* NOLINTNEXTLINE(google-objc-avoid-throwing-exception) */ \
@throw; \
}@finally{ \
recorder = [OCMMacroState endRejectMacro]; \
} \
recorder; \
); \
})
#define OCMClassMethod(invocation) \
_OCMSilenceWarnings( \
[[OCMMacroState globalState] switchToClassMethod]; \
invocation; \
);
#ifndef OCM_DISABLE_SHORT_SYNTAX
#define ClassMethod(invocation) OCMClassMethod(invocation)
#endif
#define OCMVerifyAll(mock) [(OCMockObject *)mock verifyAtLocation:OCMMakeLocation(self, __FILE__, __LINE__)]
#define OCMVerifyAllWithDelay(mock, delay) [(OCMockObject *)mock verifyWithDelay:delay atLocation:OCMMakeLocation(self, __FILE__, __LINE__)]
#define _OCMVerify(invocation) \
({ \
_OCMSilenceWarnings( \
[OCMMacroState beginVerifyMacroAtLocation:OCMMakeLocation(self, __FILE__, __LINE__)]; \
@try{ \
invocation; \
}@catch(...){ \
[[OCMMacroState globalState] setInvocationDidThrow:YES]; \
/* NOLINTNEXTLINE(google-objc-avoid-throwing-exception) */ \
@throw; \
}@finally{ \
[OCMMacroState endVerifyMacro]; \
} \
); \
})
#define _OCMVerifyWithQuantifier(quantifier, invocation) \
({ \
_OCMSilenceWarnings( \
[OCMMacroState beginVerifyMacroAtLocation:OCMMakeLocation(self, __FILE__, __LINE__) withQuantifier:quantifier]; \
@try{ \
invocation; \
}@catch(...){ \
[[OCMMacroState globalState] setInvocationDidThrow:YES]; \
/* NOLINTNEXTLINE(google-objc-avoid-throwing-exception) */ \
@throw; \
}@finally{ \
[OCMMacroState endVerifyMacro]; \
} \
); \
})
// explanation for macros below here: https://stackoverflow.com/questions/3046889/optional-parameters-with-c-macros
#define _OCMVerify_1(A) _OCMVerify(A)
#define _OCMVerify_2(A,B) _OCMVerifyWithQuantifier(A, B)
#define _OCMVerify_X(x,A,B,FUNC, ...) FUNC
#define OCMVerify(...) _OCMVerify_X(,##__VA_ARGS__, _OCMVerify_2(__VA_ARGS__), _OCMVerify_1(__VA_ARGS__))
#define _OCMSilenceWarnings(macro) \
({ \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wunused-value\"") \
_Pragma("clang diagnostic ignored \"-Wunused-getter-return-value\"") \
_Pragma("clang diagnostic ignored \"-Wstrict-selector-match\"") \
macro \
_Pragma("clang diagnostic pop") \
})
================================================
FILE: Pods/OCMock/Source/OCMock/OCMockObject.h
================================================
/*
* Copyright (c) 2004-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
@class OCMLocation;
@class OCMQuantifier;
@class OCMInvocationStub;
@class OCMStubRecorder;
@class OCMInvocationMatcher;
@class OCMInvocationExpectation;
@interface OCMockObject : NSProxy
{
BOOL isNice;
BOOL expectationOrderMatters;
NSMutableArray *stubs;
NSMutableArray *expectations;
NSMutableArray *exceptions;
NSMutableArray *invocations;
}
+ (id)mockForClass:(Class)aClass;
+ (id)mockForProtocol:(Protocol *)aProtocol;
+ (id)partialMockForObject:(NSObject *)anObject;
+ (id)niceMockForClass:(Class)aClass;
+ (id)niceMockForProtocol:(Protocol *)aProtocol;
+ (id)observerMock __deprecated_msg("Please use XCTNSNotificationExpectation instead.");
- (instancetype)init;
- (void)setExpectationOrderMatters:(BOOL)flag;
- (id)stub;
- (id)expect;
- (id)reject;
- (id)verify;
- (id)verifyAtLocation:(OCMLocation *)location;
- (void)verifyWithDelay:(NSTimeInterval)delay;
- (void)verifyWithDelay:(NSTimeInterval)delay atLocation:(OCMLocation *)location;
- (void)stopMocking;
// internal use only
- (void)addStub:(OCMInvocationStub *)aStub;
- (void)addExpectation:(OCMInvocationExpectation *)anExpectation;
- (void)addInvocation:(NSInvocation *)anInvocation;
- (BOOL)handleInvocation:(NSInvocation *)anInvocation;
- (void)handleUnRecordedInvocation:(NSInvocation *)anInvocation;
- (BOOL)handleSelector:(SEL)sel;
- (void)verifyInvocation:(OCMInvocationMatcher *)matcher;
- (void)verifyInvocation:(OCMInvocationMatcher *)matcher atLocation:(OCMLocation *)location;
- (void)verifyInvocation:(OCMInvocationMatcher *)matcher withQuantifier:(OCMQuantifier *)quantifier atLocation:(OCMLocation *)location;
- (NSString *)descriptionForVerificationFailureWithMatcher:(OCMInvocationMatcher *)matcher quantifier:(OCMQuantifier *)quantifier invocationCount:(NSUInteger)count;
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCMockObject.m
================================================
/*
* Copyright (c) 2004-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "NSInvocation+OCMAdditions.h"
#import "OCMockObject.h"
#import "OCClassMockObject.h"
#import "OCMExceptionReturnValueProvider.h"
#import "OCMExpectationRecorder.h"
#import "OCMFunctionsPrivate.h"
#import "OCMInvocationExpectation.h"
#import "OCMLocation.h"
#import "OCMMacroState.h"
#import "OCMQuantifier.h"
#import "OCMVerifier.h"
#import "OCObserverMockObject.h"
#import "OCPartialMockObject.h"
#import "OCProtocolMockObject.h"
@implementation OCMockObject
#pragma mark Class initialisation
+ (void)initialize
{
if([[NSInvocation class] instanceMethodSignatureForSelector:@selector(getArgumentAtIndexAsObject:)] == NULL)
[NSException raise:NSInternalInconsistencyException format:@"** Expected method not present; the method getArgumentAtIndexAsObject: is not implemented by NSInvocation. If you see this exception it is likely that you are using the static library version of OCMock and your project is not configured correctly to load categories from static libraries. Did you forget to add the -ObjC linker flag?"];
}
#pragma mark Factory methods
+ (id)mockForClass:(Class)aClass
{
return [[[OCClassMockObject alloc] initWithClass:aClass] autorelease];
}
+ (id)mockForProtocol:(Protocol *)aProtocol
{
return [[[OCProtocolMockObject alloc] initWithProtocol:aProtocol] autorelease];
}
+ (id)partialMockForObject:(NSObject *)anObject
{
return [[[OCPartialMockObject alloc] initWithObject:anObject] autorelease];
}
+ (id)niceMockForClass:(Class)aClass
{
return [self _makeNice:[self mockForClass:aClass]];
}
+ (id)niceMockForProtocol:(Protocol *)aProtocol
{
return [self _makeNice:[self mockForProtocol:aProtocol]];
}
+ (id)_makeNice:(OCMockObject *)mock
{
mock->isNice = YES;
return mock;
}
+ (id)observerMock
{
return [[[OCObserverMockObject alloc] init] autorelease];
}
#pragma mark Initialisers, description, accessors, etc.
- (instancetype)init
{
// Check whether init is called a second time, which can happen when stubbing alloc/init. Note
// that you only really stub the alloc method. Init cannot be stubbed. Invocations of init
// will always end up here, and we return self. If init is invoked inside a macro that's an
// error, which will be detected in the init method of the recorder.
if(stubs != nil)
{
// check if we are called from inside a macro
OCMRecorder *recorder = [[OCMMacroState globalState] recorder];
if(recorder != nil)
{
[recorder setMockObject:self];
return (id)[[recorder retain] init];
}
return self;
}
if([self class] == [OCMockObject class])
{
[NSException raise:NSInternalInconsistencyException format:@"*** Cannot create instances of OCMockObject. Please use one of the subclasses."];
}
// no [super init], we're inheriting from NSProxy
expectationOrderMatters = NO;
stubs = [[NSMutableArray alloc] init];
expectations = [[NSMutableArray alloc] init];
exceptions = [[NSMutableArray alloc] init];
invocations = [[NSMutableArray alloc] init];
return self;
}
- (void)dealloc
{
[stubs release];
[expectations release];
[exceptions release];
[invocations release];
[super dealloc];
}
- (NSString *)description
{
return @"OCMockObject";
}
- (void)addStub:(OCMInvocationStub *)aStub
{
[self assertInvocationsArrayIsPresent];
@synchronized(stubs)
{
[stubs addObject:aStub];
}
}
- (OCMInvocationStub *)stubForInvocation:(NSInvocation *)anInvocation
{
@synchronized(stubs)
{
for(OCMInvocationStub *stub in stubs)
if([stub matchesInvocation:anInvocation])
return stub;
return nil;
}
}
- (void)addExpectation:(OCMInvocationExpectation *)anExpectation
{
@synchronized(expectations)
{
[expectations addObject:anExpectation];
}
}
- (void)assertInvocationsArrayIsPresent
{
if(invocations == nil)
{
[NSException raise:NSInternalInconsistencyException format:@"** Cannot use mock object %@ at %p. This error usually occurs when a mock object is used after stopMocking has been called on it. In most cases it is not necessary to call stopMocking. If you know you have to, please make sure that the mock object is not used afterwards.", [self description], (void *)self];
}
}
- (void)addInvocation:(NSInvocation *)anInvocation
{
@synchronized(invocations)
{
// We can't do a normal retain arguments on anInvocation because its target/arguments/return
// value could be self. That would produce a retain cycle self->invocations->anInvocation->self.
// However we need to retain everything on anInvocation that isn't self because we expect them to
// stick around after this method returns. Use our special method to retain just what's needed.
// This still doesn't completely prevent retain cycles since any of the arguments could have a
// strong reference to self. Those will have to be broken with manual calls to -stopMocking.
[anInvocation retainObjectArgumentsExcludingObject:self];
[invocations addObject:anInvocation];
}
}
#pragma mark Public API
- (void)setExpectationOrderMatters:(BOOL)flag
{
expectationOrderMatters = flag;
}
- (void)stopMocking
{
// invocations can contain objects that clients expect to be deallocated by now,
// and they can also have a strong reference to self, creating a retain cycle. Get
// rid of all of the invocations to hopefully let their objects deallocate, and to
// break any retain cycles involving self.
@synchronized(invocations)
{
[invocations removeAllObjects];
[invocations autorelease];
invocations = nil;
}
}
- (id)stub
{
return [[[OCMStubRecorder alloc] initWithMockObject:self] autorelease];
}
- (id)expect
{
return [[[OCMExpectationRecorder alloc] initWithMockObject:self] autorelease];
}
- (id)reject
{
return [[self expect] never];
}
- (id)verify
{
return [self verifyAtLocation:nil];
}
- (id)verifyAtLocation:(OCMLocation *)location
{
NSMutableArray *unsatisfiedExpectations = [NSMutableArray array];
@synchronized(expectations)
{
for(OCMInvocationExpectation *e in expectations)
{
if(![e isSatisfied])
[unsatisfiedExpectations addObject:e];
}
}
if([unsatisfiedExpectations count] == 1)
{
NSString *description = [NSString stringWithFormat:@"%@: expected method was not invoked: %@",
[self description], [[unsatisfiedExpectations objectAtIndex:0] description]];
OCMReportFailure(location, description);
}
else if([unsatisfiedExpectations count] > 0)
{
NSString *description = [NSString stringWithFormat:@"%@: %@ expected methods were not invoked: %@",
[self description], @([unsatisfiedExpectations count]), [self _stubDescriptions:YES]];
OCMReportFailure(location, description);
}
OCMInvocationExpectation *firstException = nil;
@synchronized(exceptions)
{
firstException = [exceptions.firstObject retain];
}
if(firstException)
{
NSString *description = [NSString stringWithFormat:@"%@: %@ (This is a strict mock failure that was ignored when it actually occurred.)",
[self description], [firstException description]];
OCMReportFailure(location, description);
}
[firstException release];
return [[[OCMVerifier alloc] initWithMockObject:self] autorelease];
}
- (void)verifyWithDelay:(NSTimeInterval)delay
{
[self verifyWithDelay:delay atLocation:nil];
}
- (void)verifyWithDelay:(NSTimeInterval)delay atLocation:(OCMLocation *)location
{
NSTimeInterval step = 0.01;
while(delay > 0)
{
@synchronized(expectations)
{
BOOL allExpectationsAreMatchAndReject = YES;
for(OCMInvocationExpectation *expectation in expectations)
{
if(![expectation isMatchAndReject])
{
allExpectationsAreMatchAndReject = NO;
break;
}
}
if(allExpectationsAreMatchAndReject)
break;
}
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:MIN(step, delay)]];
delay -= step;
step *= 2;
}
[self verifyAtLocation:location];
}
#pragma mark Verify after running
- (void)verifyInvocation:(OCMInvocationMatcher *)matcher
{
[self verifyInvocation:matcher atLocation:nil];
}
- (void)verifyInvocation:(OCMInvocationMatcher *)matcher atLocation:(OCMLocation *)location
{
[self verifyInvocation:matcher withQuantifier:nil atLocation:location];
}
- (void)verifyInvocation:(OCMInvocationMatcher *)matcher withQuantifier:(OCMQuantifier *)quantifier atLocation:(OCMLocation *)location
{
NSUInteger count = 0;
[self assertInvocationsArrayIsPresent];
@synchronized(invocations)
{
for(NSInvocation *invocation in invocations)
{
if([matcher matchesInvocation:invocation])
count += 1;
}
}
if(quantifier == nil)
quantifier = [OCMQuantifier atLeast:1];
if(![quantifier isValidCount:count])
{
NSString *description = [self descriptionForVerificationFailureWithMatcher:matcher quantifier:quantifier invocationCount:count];
OCMReportFailure(location, description);
}
}
- (NSString *)descriptionForVerificationFailureWithMatcher:(OCMInvocationMatcher *)matcher quantifier:(OCMQuantifier *)quantifier invocationCount:(NSUInteger)count
{
NSString *actualDescription = nil;
switch(count)
{
case 0: actualDescription = @"not invoked"; break;
case 1: actualDescription = @"invoked once"; break;
default: actualDescription = [NSString stringWithFormat:@"invoked %lu times", (unsigned long)count]; break;
}
return [NSString stringWithFormat:@"%@: Method `%@` was %@; but was expected %@.",
[self description], [matcher description], actualDescription, [quantifier description]];
}
#pragma mark Handling invocations
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if([OCMMacroState globalState] != nil)
{
OCMRecorder *recorder = [[OCMMacroState globalState] recorder];
[recorder setMockObject:self];
// In order for ARC to work correctly, the recorder has to set up return values for
// methods in the init family of methods. If the mock forwards a method to the recorder
// that it will record, i.e. a method that the recorder does not implement, then the
// recorder must set the mock as the return value. Otherwise it must use itself.
[recorder setShouldReturnMockFromInit:(class_getInstanceMethod(object_getClass(recorder), aSelector) == NO)];
return recorder;
}
return nil;
}
- (BOOL)handleSelector:(SEL)sel
{
@synchronized(stubs)
{
for(OCMInvocationStub *recorder in stubs)
if([recorder matchesSelector:sel])
return YES;
}
return NO;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
@try
{
if([self handleInvocation:anInvocation] == NO)
[self handleUnRecordedInvocation:anInvocation];
}
@catch(NSException *e)
{
if([[e name] isEqualToString:OCMStubbedException])
{
e = [[e userInfo] objectForKey:@"exception"];
}
else
{
// add non-stubbed method to list of exceptions to be re-raised in verify
@synchronized(exceptions)
{
[exceptions addObject:e];
}
}
[e raise];
}
}
- (BOOL)handleInvocation:(NSInvocation *)anInvocation
{
[self assertInvocationsArrayIsPresent];
[self addInvocation:anInvocation];
OCMInvocationStub *stub = [self stubForInvocation:anInvocation];
if(stub == nil)
return NO;
// Retain the stub in case it ends up being removed because we still need it at the end for handleInvocation:
[stub retain];
BOOL removeStub = NO;
@synchronized(expectations)
{
if([expectations containsObject:stub])
{
OCMInvocationExpectation *expectation = [self _nextExpectedInvocation];
if(expectationOrderMatters && (expectation != stub))
{
[NSException raise:NSInternalInconsistencyException
format:@"%@: unexpected method invoked: %@\n\texpected:\t%@",
[self description], [stub description], [[expectations objectAtIndex:0] description]];
}
// We can't check isSatisfied yet, since the stub won't be satisfied until we call
// handleInvocation: since we'll still have the current expectation in the expectations array, which
// will cause an exception if expectationOrderMatters is YES and we're not ready for any future
// expected methods to be called yet
if(![(OCMInvocationExpectation *)stub isMatchAndReject])
{
[expectations removeObject:stub];
removeStub = YES;
}
}
}
if(removeStub)
{
@synchronized(stubs)
{
[stubs removeObject:stub];
}
}
@try
{
[stub handleInvocation:anInvocation];
}
@finally
{
[stub release];
}
return YES;
}
// Must be synchronized on expectations when calling this method.
- (OCMInvocationExpectation *)_nextExpectedInvocation
{
for(OCMInvocationExpectation *expectation in expectations)
if(![expectation isMatchAndReject])
return expectation;
return nil;
}
- (void)handleUnRecordedInvocation:(NSInvocation *)anInvocation
{
if(isNice == NO)
{
[NSException raise:NSInternalInconsistencyException
format:@"%@: unexpected method invoked: %@ %@",
[self description], [anInvocation invocationDescription], [self _stubDescriptions:NO]];
}
}
- (void)doesNotRecognizeSelector:(SEL)aSelector __unused
{
if([OCMMacroState globalState] != nil)
{
// we can't do anything clever with the macro state because we must raise an exception here
[NSException raise:NSInvalidArgumentException
format:@"%@: Cannot stub/expect/verify method '%@' because no such method exists in the mocked class.",
[self description], NSStringFromSelector(aSelector)];
}
else
{
[NSException raise:NSInvalidArgumentException
format:@"-[%@ %@]: unrecognized selector sent to instance %p",
[self description], NSStringFromSelector(aSelector), (void *)self];
}
}
#pragma mark Helper methods
- (NSString *)_stubDescriptions:(BOOL)onlyExpectations
{
NSMutableString *outputString = [NSMutableString string];
NSArray *stubsCopy = nil;
@synchronized(stubs)
{
stubsCopy = [stubs copy];
}
for(OCMStubRecorder *stub in stubsCopy)
{
BOOL expectationsContainStub = NO;
@synchronized(expectations)
{
expectationsContainStub = [expectations containsObject:stub];
}
NSString *prefix = @"";
if(onlyExpectations)
{
if(expectationsContainStub == NO)
continue;
}
else
{
if(expectationsContainStub)
prefix = @"expected:\t";
else
prefix = @"stubbed:\t";
}
[outputString appendFormat:@"\n\t%@%@", prefix, [stub description]];
}
[stubsCopy release];
return outputString;
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCObserverMockObject.h
================================================
/*
* Copyright (c) 2009-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
@class OCMLocation;
__deprecated_msg("Please use XCTNSNotificationExpectation instead.")
@interface OCObserverMockObject : NSObject
{
BOOL expectationOrderMatters;
NSMutableArray *recorders;
NSMutableArray *centers;
}
- (void)setExpectationOrderMatters:(BOOL)flag;
- (id)expect;
- (void)verify;
- (void)verifyAtLocation:(OCMLocation *)location;
- (void)handleNotification:(NSNotification *)aNotification;
// internal use
- (void)autoRemoveFromCenter:(NSNotificationCenter *)aCenter;
- (NSNotification *)notificationWithName:(NSString *)name object:(id)sender;
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCObserverMockObject.m
================================================
/*
* Copyright (c) 2009-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCObserverMockObject.h"
#import "OCMFunctionsPrivate.h"
#import "OCMLocation.h"
#import "OCMMacroState.h"
#import "OCMObserverRecorder.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-implementations"
@implementation OCObserverMockObject
#pragma clang diagnostic pop
#pragma mark Initialisers, description, accessors, etc.
- (id)init
{
if((self = [super init]))
{
recorders = [[NSMutableArray alloc] init];
centers = [[NSMutableArray alloc] init];
}
return self;
}
- (id)retain
{
return [super retain];
}
- (void)dealloc
{
for(NSNotificationCenter *c in centers)
[c removeObserver:self];
[centers release];
[recorders release];
[super dealloc];
}
- (NSString *)description
{
return @"OCObserverMockObject";
}
- (void)setExpectationOrderMatters:(BOOL)flag
{
expectationOrderMatters = flag;
}
- (void)autoRemoveFromCenter:(NSNotificationCenter *)aCenter
{
@synchronized(centers)
{
[centers addObject:aCenter];
}
}
#pragma mark Public API
- (id)expect
{
OCMObserverRecorder *recorder = [[[OCMObserverRecorder alloc] init] autorelease];
@synchronized(recorders)
{
[recorders addObject:recorder];
}
return recorder;
}
- (void)verify
{
[self verifyAtLocation:nil];
}
- (void)verifyAtLocation:(OCMLocation *)location
{
@synchronized(recorders)
{
if([recorders count] == 1)
{
NSString *description = [NSString stringWithFormat:@"%@: expected notification was not observed: %@",
[self description], [[recorders lastObject] description]];
OCMReportFailure(location, description);
}
else if([recorders count] > 0)
{
NSString *description = [NSString stringWithFormat:@"%@ : %@ expected notifications were not observed.",
[self description], @([recorders count])];
OCMReportFailure(location, description);
}
}
}
#pragma mark Receiving recording requests via macro
// This is a bit of a hack. The methods simply assume that when they are called from within a macro that it's
// the OCMExpect macro. That creates a recorder for mock objects, which we cannot use here. So, we overwrite
// it with a newly allocated recorder.
- (NSNotification *)notificationWithName:(NSString *)name object:(id)sender
{
if([OCMMacroState globalState] != nil)
{
id recorder = [self expect];
[[OCMMacroState globalState] setRecorder:recorder];
return [recorder notificationWithName:name object:sender];
}
return nil;
}
- (NSNotification *)notificationWithName:(NSString *)name object:(id)sender userInfo:(NSDictionary *)userInfo
{
if([OCMMacroState globalState] != nil)
{
id recorder = [self expect];
[[OCMMacroState globalState] setRecorder:recorder];
return [recorder notificationWithName:name object:sender userInfo:userInfo];
}
return nil;
}
#pragma mark Receiving notifications
- (void)handleNotification:(NSNotification *)aNotification
{
@synchronized(recorders)
{
NSUInteger i, limit;
limit = expectationOrderMatters ? 1 : [recorders count];
for(i = 0; i < limit; i++)
{
if([[recorders objectAtIndex:i] matchesNotification:aNotification])
{
[recorders removeObjectAtIndex:i];
return;
}
}
}
[NSException raise:NSInternalInconsistencyException
format:@"%@: unexpected notification observed: %@", [self description],
[aNotification description]];
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCPartialMockObject.h
================================================
/*
* Copyright (c) 2009-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCClassMockObject.h"
@interface OCPartialMockObject : OCClassMockObject
{
NSObject *realObject;
NSInvocation *invocationFromMock;
}
- (id)initWithObject:(NSObject *)anObject;
- (NSObject *)realObject;
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCPartialMockObject.m
================================================
/*
* Copyright (c) 2009-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
#import "NSInvocation+OCMAdditions.h"
#import "NSMethodSignature+OCMAdditions.h"
#import "NSObject+OCMAdditions.h"
#import "OCPartialMockObject.h"
#import "OCMFunctionsPrivate.h"
#import "OCMInvocationStub.h"
@implementation OCPartialMockObject
#pragma mark Initialisers, description, accessors, etc.
- (id)initWithObject:(NSObject *)anObject
{
if(anObject == nil)
[NSException raise:NSInvalidArgumentException format:@"Object cannot be nil."];
if([anObject isProxy])
[NSException raise:NSInvalidArgumentException format:@"OCMock does not support partially mocking subclasses of NSProxy."];
Class const class = [self classToSubclassForObject:anObject];
[super initWithClass:class];
realObject = [anObject retain];
[self prepareObjectForInstanceMethodMocking];
return self;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"OCPartialMockObject(%@)", NSStringFromClass(mockedClass)];
}
- (NSObject *)realObject
{
return realObject;
}
#pragma mark Helper methods
- (void)assertClassIsSupported:(Class)class
{
[super assertClassIsSupported:class];
NSString *classname = NSStringFromClass(class);
NSString *reason = nil;
if([classname hasPrefix:@"__NSTagged"] || [classname hasPrefix:@"NSTagged"])
reason = [NSString stringWithFormat:@"OCMock does not support partially mocking tagged classes; got %@", classname];
else if([classname hasPrefix:@"__NSCF"])
reason = [NSString stringWithFormat:@"OCMock does not support partially mocking toll-free bridged classes; got %@", classname];
if(reason != nil)
[[NSException exceptionWithName:NSInvalidArgumentException reason:reason userInfo:nil] raise];
}
- (Class)classToSubclassForObject:(id)object
{
if([object observationInfo] != NULL)
{
// Special treatment for objects that are observed with KVO. The KVO implementation sets
// a subclass for such objects and it overrides the -class method to return the original
// class. If we base our subclass on the KVO subclass, as returned by object_getClass(),
// crashes will occur. So, we take the real class instead. Unfortunately, this removes
// any observers set up before.
NSLog(@"Warning: Creating a partial mock for %@. This object has observers, which will now stop receiving KVO notifications. If you want to receive KVO notifications, create the partial mock first, and then register the observer.", object);
return [object class];
}
return object_getClass(object);
}
#pragma mark Extending/overriding superclass behaviour
- (void)stopMocking
{
if(realObject != nil)
{
Class partialMockClass = object_getClass(realObject);
OCMSetAssociatedMockForObject(nil, realObject);
object_setClass(realObject, [self mockedClass]);
[realObject release];
realObject = nil;
OCMDisposeSubclass(partialMockClass);
}
[super stopMocking];
}
- (void)addStub:(OCMInvocationStub *)aStub
{
[super addStub:aStub];
if(![aStub recordedAsClassMethod])
[self setupForwarderForSelector:[[aStub recordedInvocation] selector]];
}
- (void)addInvocation:(NSInvocation *)anInvocation
{
// If the mock invokes a method on the real object we end up here a second time, but because
// the mock has added the invocation already we do not want to add it again.
if((invocationFromMock == nil) || ([anInvocation selector] != [invocationFromMock selector]))
[super addInvocation:anInvocation];
}
- (void)handleUnRecordedInvocation:(NSInvocation *)anInvocation
{
// In the case of an init that is called on a mock we must return the mock instance and
// not the realObject if the underlying init returns the realObject because at the call site
// ARC will have retained the target and the release/retain count must balance. If we return
// the realObject, then realObject will be over released and the mock will leak. Equally if
// we are called on the realObject we need to make sure not to return the mock.
id targetReceivingInit = nil;
if([anInvocation methodIsInInitFamily])
{
targetReceivingInit = [anInvocation target];
[realObject retain];
}
invocationFromMock = anInvocation;
[anInvocation invokeWithTarget:realObject];
invocationFromMock = nil;
if(targetReceivingInit)
{
id returnVal;
[anInvocation getReturnValue:&returnVal];
if(returnVal == realObject)
{
[anInvocation setReturnValue:&self];
[realObject release];
[self retain];
}
#ifndef __clang_analyzer__
// see #456 for details
[targetReceivingInit release];
#endif
}
}
#pragma mark Subclass management
- (void)prepareObjectForInstanceMethodMocking
{
OCMSetAssociatedMockForObject(self, realObject);
/* dynamically create a subclass and set it as the class of the object */
Class subclass = OCMCreateSubclass(mockedClass, realObject);
object_setClass(realObject, subclass);
/* point forwardInvocation: of the object to the implementation in the mock */
Method myForwardMethod = class_getInstanceMethod([self mockObjectClass], @selector(forwardInvocationForRealObject:));
IMP myForwardIMP = method_getImplementation(myForwardMethod);
class_addMethod(subclass, @selector(forwardInvocation:), myForwardIMP, method_getTypeEncoding(myForwardMethod));
/* do the same for forwardingTargetForSelector, remember existing imp with alias selector */
Method myForwardingTargetMethod = class_getInstanceMethod([self mockObjectClass], @selector(forwardingTargetForSelectorForRealObject:));
IMP myForwardingTargetIMP = method_getImplementation(myForwardingTargetMethod);
IMP originalForwardingTargetIMP = [mockedClass instanceMethodForSelector:@selector(forwardingTargetForSelector:)];
class_addMethod(subclass, @selector(forwardingTargetForSelector:), myForwardingTargetIMP, method_getTypeEncoding(myForwardingTargetMethod));
class_addMethod(subclass, @selector(ocmock_replaced_forwardingTargetForSelector:), originalForwardingTargetIMP, method_getTypeEncoding(myForwardingTargetMethod));
/* We also override the -class method to return the original class */
Method myObjectClassMethod = class_getInstanceMethod([self mockObjectClass], @selector(classForRealObject));
const char *objectClassTypes = method_getTypeEncoding(myObjectClassMethod);
IMP myObjectClassImp = method_getImplementation(myObjectClassMethod);
class_addMethod(subclass, @selector(class), myObjectClassImp, objectClassTypes);
/* Adding forwarder for most instance methods to allow for verify after run */
NSArray *methodsNotToForward = @[ @"class", @"forwardingTargetForSelector:", @"methodSignatureForSelector:", @"forwardInvocation:",
@"allowsWeakReference", @"retainWeakReference", @"isBlock", @"retainCount", @"retain", @"release", @"autorelease", @".cxx_construct", @".cxx_destruct" ];
void (^setupForwarderFiltered)(Class, SEL) = ^(Class cls, SEL sel) {
if(OCMIsAppleBaseClass(cls) || OCMIsApplePrivateMethod(cls, sel))
return;
if([methodsNotToForward containsObject:NSStringFromSelector(sel)])
return;
@try
{
[self setupForwarderForSelector:sel];
}
@catch(NSException *e)
{
// ignore for now
}
};
[NSObject enumerateMethodsInClass:mockedClass usingBlock:setupForwarderFiltered];
}
- (void)setupForwarderForSelector:(SEL)sel
{
SEL aliasSelector = OCMAliasForOriginalSelector(sel);
if(class_getInstanceMethod(object_getClass(realObject), aliasSelector) != NULL)
return;
Method originalMethod = class_getInstanceMethod(mockedClass, sel);
/* Might be NULL if the selector is forwarded to another class */
IMP originalIMP = (originalMethod != NULL) ? method_getImplementation(originalMethod) : NULL;
const char *types = (originalMethod != NULL) ? method_getTypeEncoding(originalMethod) : NULL;
// TODO: check the fallback implementation is actually sufficient
if(types == NULL)
types = ([[mockedClass instanceMethodSignatureForSelector:sel] fullObjCTypes]);
Class subclass = object_getClass([self realObject]);
IMP forwarderIMP = [mockedClass instanceMethodForwarderForSelector:sel];
class_replaceMethod(subclass, sel, forwarderIMP, types);
class_addMethod(subclass, aliasSelector, originalIMP, types);
}
// Implementation of the -class method; return the Class that was reported with [realObject class] prior to mocking
- (Class)classForRealObject
{
// in here "self" is a reference to the real object, not the mock
OCPartialMockObject *mock = OCMGetAssociatedMockForObject(self);
if(mock == nil)
[NSException raise:NSInternalInconsistencyException format:@"No partial mock for object %p", self];
return [mock mockedClass];
}
- (id)forwardingTargetForSelectorForRealObject:(SEL)sel
{
// in here "self" is a reference to the real object, not the mock
OCPartialMockObject *mock = OCMGetAssociatedMockForObject(self);
if(mock == nil)
[NSException raise:NSInternalInconsistencyException format:@"No partial mock for object %p", self];
if([mock handleSelector:sel])
return self;
return [self ocmock_replaced_forwardingTargetForSelector:sel];
}
// Make the compiler happy in -forwardingTargetForSelectorForRealObject: because it can't find the message…
- (id)ocmock_replaced_forwardingTargetForSelector:(SEL)sel
{
return nil;
}
- (void)forwardInvocationForRealObject:(NSInvocation *)anInvocation
{
// in here "self" is a reference to the real object, not the mock
OCPartialMockObject *mock = OCMGetAssociatedMockForObject(self);
if(mock == nil)
[NSException raise:NSInternalInconsistencyException format:@"No partial mock for object %p", self];
if([mock handleInvocation:anInvocation] == NO)
{
[anInvocation setSelector:OCMAliasForOriginalSelector([anInvocation selector])];
[anInvocation invoke];
}
}
#pragma mark Verification handling
- (NSString *)descriptionForVerificationFailureWithMatcher:(OCMInvocationMatcher *)matcher quantifier:(OCMQuantifier *)quantifier invocationCount:(NSUInteger)count
{
SEL matcherSel = [[matcher recordedInvocation] selector];
__block BOOL stubbingMightHelp = NO;
[NSObject enumerateMethodsInClass:mockedClass usingBlock:^(Class cls, SEL sel) {
if(sel == matcherSel)
stubbingMightHelp = OCMIsAppleBaseClass(cls) || OCMIsApplePrivateMethod(cls, sel);
}];
NSString *description = [super descriptionForVerificationFailureWithMatcher:matcher quantifier:quantifier invocationCount:count];
if(stubbingMightHelp)
{
description = [description stringByAppendingFormat:@" Adding a stub for the method may resolve the issue, e.g. `OCMStub([mockObject %@]).andForwardToRealObject()`", [matcher description]];
}
return description;
}
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCProtocolMockObject.h
================================================
/*
* Copyright (c) 2005-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import "OCMockObject.h"
@interface OCProtocolMockObject : OCMockObject
{
Protocol *mockedProtocol;
}
- (id)initWithProtocol:(Protocol *)aProtocol;
@end
================================================
FILE: Pods/OCMock/Source/OCMock/OCProtocolMockObject.m
================================================
/*
* Copyright (c) 2005-2021 Erik Doernenburg and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use these files except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
#import
#import "OCProtocolMockObject.h"
@implementation OCProtocolMockObject
#pragma mark Initialisers, description, accessors, etc.
- (id)initWithProtocol:(Protocol *)aProtocol
{
if(aProtocol == nil)
[NSException raise:NSInvalidArgumentException format:@"Protocol cannot be nil."];
[super init];
mockedProtocol = aProtocol;
return self;
}
- (NSString *)description
{
const char *name = protocol_getName(mockedProtocol);
return [NSString stringWithFormat:@"OCProtocolMockObject(%s)", name];
}
#pragma mark Proxy API
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
struct { BOOL isRequired; BOOL isInstance; } opts[4] = { {YES, YES}, {NO, YES}, {YES, NO}, {NO, NO} };
for(int i = 0; i < 4; i++)
{
struct objc_method_description methodDescription = protocol_getMethodDescription(mockedProtocol, aSelector, opts[i].isRequired, opts[i].isInstance);
if(methodDescription.name != NULL)
return [NSMethodSignature signatureWithObjCTypes:methodDescription.types];
}
return nil;
}
- (BOOL)conformsToProtocol:(Protocol *)aProtocol
{
return protocol_conformsToProtocol(mockedProtocol, aProtocol);
}
- (BOOL)respondsToSelector:(SEL)selector
{
return ([self methodSignatureForSelector:selector] != nil);
}
@end
================================================
FILE: Pods/Pods.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objects = {
/* Begin PBXBuildFile section */
000A3A1711489B7F1981551FB9D5FF42 /* TyphoonFactoryDefinition.m in Sources */ = {isa = PBXBuildFile; fileRef = F813C3F784E8340DED279F0AC918CD92 /* TyphoonFactoryDefinition.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
009A101C6D6749EEE990C935A028904B /* UICollectionReusableView+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 87D6C46EF50641EB979304EC92ADD341 /* UICollectionReusableView+RACSignalSupport.m */; };
00DAE48C9A4FBCD1FCAA922CA57B45F9 /* SDWebImageDownloaderRequestModifier.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BE4EBEC7840EB2B41361CE32163EA16 /* SDWebImageDownloaderRequestModifier.m */; };
01337B28102993C3FDD41D9A2E0AFAB2 /* RACCompoundDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B2D3D98B9F08D80C8134D1C5346BA57 /* RACCompoundDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; };
01AD9C4D5168F89B9844CB3E46072DE7 /* NSObject+RACDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = B2042CA0D12A3305ACABBBC22E60BE48 /* NSObject+RACDescription.m */; };
01B2E81FBD9A6D179150C9130B2C1807 /* UIRefreshControl+RACCommandSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 9267DAB90224A45DA4C991474C964341 /* UIRefreshControl+RACCommandSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
026C6439062428052B58FA8D41877DB1 /* OCMBlockArgCaller.m in Sources */ = {isa = PBXBuildFile; fileRef = 1610B6558D631E16BFCF0FD099283BDA /* OCMBlockArgCaller.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
0298EAC75B6300AD806B86273DE13278 /* TyphoonStoryboardResolver.m in Sources */ = {isa = PBXBuildFile; fileRef = 004DB416A9430FE442A136525FC3D93D /* TyphoonStoryboardResolver.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
02BDC4A577B91415561618A54DCB721E /* TyphoonPatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F8278A4894BA9FD972FEC5FCB716F39 /* TyphoonPatcher.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
037B74553C43333B8693ACA61DB05C78 /* OCMRealObjectForwarder.h in Headers */ = {isa = PBXBuildFile; fileRef = 45BD3FAB4630B2990E6B8F1BC78D9D8F /* OCMRealObjectForwarder.h */; settings = {ATTRIBUTES = (Project, ); }; };
03815ACD5E64B92672AE8AA3A5A2EE27 /* OCMArg.h in Headers */ = {isa = PBXBuildFile; fileRef = FB65E6E7D6717ADB26576EA05A351211 /* OCMArg.h */; settings = {ATTRIBUTES = (Public, ); }; };
03A1468408B50A609EF3669C178B99D4 /* OCMInvocationExpectation.m in Sources */ = {isa = PBXBuildFile; fileRef = 80863B62A376A21783063A91F74E52C6 /* OCMInvocationExpectation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
03E6A871ACAE01F6AD2BFD0079FC0664 /* TyphoonNSURLTypeConverter.h in Headers */ = {isa = PBXBuildFile; fileRef = 98854B7F25ABB6BE45BB69C58166AA5E /* TyphoonNSURLTypeConverter.h */; settings = {ATTRIBUTES = (Public, ); }; };
042D40751BD2F51FBE9FECD4707CBBE9 /* SDDeviceHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 92A096C440891899FBCBB081ED6C8474 /* SDDeviceHelper.h */; settings = {ATTRIBUTES = (Private, ); }; };
0453019EC6578A67B82CF569EC765546 /* SDFileAttributeHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = BAF592AE664D056D895A926E0F7D0261 /* SDFileAttributeHelper.h */; settings = {ATTRIBUTES = (Private, ); }; };
04743C039FBAF096D2EFAF6CBBA24BE0 /* TyphoonFactoryAutoInjectionPostProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = F0E38DEA6A48AB6D0B5359119070CCD3 /* TyphoonFactoryAutoInjectionPostProcessor.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
0510DC02DA8CA59E8F2E373AF813BDB6 /* TyphoonBundledImageTypeConverter.m in Sources */ = {isa = PBXBuildFile; fileRef = B7B1F42980891C0D9253DA6E3EEDE467 /* TyphoonBundledImageTypeConverter.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
054C3B2C429E6F3BDCDA2A480D917F51 /* RACMulticastConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 2E023F08FB04D7249EE7E6B1E84A12DC /* RACMulticastConnection.m */; };
055CCD518759FBBD7DFD0B7C079A428B /* NSObject+FactoryHooks.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B0FD681CCD09F3DE9E1B8663D3F1CC2 /* NSObject+FactoryHooks.h */; settings = {ATTRIBUTES = (Public, ); }; };
05E2B7C1DB7528A0BBEA1521BE0DBAF1 /* MASViewAttribute.h in Headers */ = {isa = PBXBuildFile; fileRef = E928F67B2793E36DB0271AE60B7C1747 /* MASViewAttribute.h */; settings = {ATTRIBUTES = (Public, ); }; };
05F22F8B6A39ADEE5F919070E6B2B869 /* UITextField+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 1428E07DEC90DAD4F228BB35096D20BF /* UITextField+RACSignalSupport.m */; };
06C4E233E7977DB81A24482E69B2D7D7 /* UIImage+Transform.m in Sources */ = {isa = PBXBuildFile; fileRef = 026AD1F39E6F56A1374F84466603518D /* UIImage+Transform.m */; };
07BC2019BAA4D31E62D7624C8E4F22E8 /* TyphoonBlockComponentFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 533ECD8A38FC36466554EF43B205AD18 /* TyphoonBlockComponentFactory.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
07C6B68367DDC6BCD78E44FFBCB8F379 /* TyphoonAbstractDetachableComponentFactoryPostProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = B2A8FF4E1BDBA3D59428BAC8FAA55FFD /* TyphoonAbstractDetachableComponentFactoryPostProcessor.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
08495F2DAFA36769FC16758CFDAC1F67 /* GCDAsyncUdpSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 7C4D52064EBE28BCC66250183EE0A11E /* GCDAsyncUdpSocket.m */; };
08699A51E1CCDD2B8DD889A284ABE884 /* RACEXTScope.h in Headers */ = {isa = PBXBuildFile; fileRef = BCD5C7A53B74E13B1712A968B6AD9302 /* RACEXTScope.h */; settings = {ATTRIBUTES = (Public, ); }; };
086E51FFE1FBD5D2C964EC8F7C1C1F5A /* Typhoon-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 8C35F5A9D87E1BF9E90AD737FA10D987 /* Typhoon-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
089F3C4BAA46A37EC5763DD312771021 /* SDImageIOAnimatedCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = C3E573EB2C906BB6045C58BC27C4CFB2 /* SDImageIOAnimatedCoder.h */; settings = {ATTRIBUTES = (Public, ); }; };
08D50C5AC969A3701B6F9137CF3A10F1 /* UIImage+ForceDecode.m in Sources */ = {isa = PBXBuildFile; fileRef = 1083669FB5354500A46F16186007AE49 /* UIImage+ForceDecode.m */; };
08E8B7ECC821E18E4B5B4F746E7337CD /* UIRefreshControl+RACCommandSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 78AC2407E9C7F573D2B8F2765AD61982 /* UIRefreshControl+RACCommandSupport.m */; };
09A2ACBC8CE1761652EAA20886AEFE10 /* SDImageCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = E1B9673A5D86AD933259E18FA172B615 /* SDImageCoder.h */; settings = {ATTRIBUTES = (Public, ); }; };
09C8A4EE1CC2BC62C3BC5EAC4F9E3BFD /* TyphoonInject.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A672A6EEB28F3A0ADB373CDCB3697AE /* TyphoonInject.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
09E7EED01CBCF945678E3D23B23EC0DD /* TyphoonConfigPostProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A3B0D97818AFB9EC630C3B522130631 /* TyphoonConfigPostProcessor.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
0B0E6CECDF516BC83756C1D5515A725B /* SDAsyncBlockOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 977CC2B85F6C66B4997C1A77F749621E /* SDAsyncBlockOperation.m */; };
0B222396072B294B46CBB818B208CC48 /* TyphoonComponentFactory+InstanceBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 05C311CA83E10EC09FC2A8CAAF090A30 /* TyphoonComponentFactory+InstanceBuilder.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
0BF77394EAA86673E7948ECD9871D89F /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D93216F983293A53686F68A6B9A39E70 /* Security.framework */; };
0C2B2878E7CF92392FFBFBD12C748255 /* TyphoonBundleResource.m in Sources */ = {isa = PBXBuildFile; fileRef = 15B1114AA6F4793852697F0F588E7204 /* TyphoonBundleResource.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
0C6956D9B1BAF797C380C6AEA4C23A5D /* RACBehaviorSubject.h in Headers */ = {isa = PBXBuildFile; fileRef = 16BEA40224A6DCCBB7E983D78F3CE027 /* RACBehaviorSubject.h */; settings = {ATTRIBUTES = (Public, ); }; };
0C994CE9E9CC8E7B31B2C862DB66E501 /* OCMNonRetainingObjectReturnValueProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = B663AA0974236ABB5B164B3F2FCA9874 /* OCMNonRetainingObjectReturnValueProvider.h */; settings = {ATTRIBUTES = (Project, ); }; };
0CD72127DA641AD9C10CF2DB2D352C6D /* CocoaAsyncSocket-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FFAB3AC801B7EA6B022EBB03D54462C /* CocoaAsyncSocket-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
0D45C478B6225E26D568D6CA97DA18FA /* OCClassMockObject.h in Headers */ = {isa = PBXBuildFile; fileRef = B7CC02F4E9B33CCE0CE749A8F5F1ACA7 /* OCClassMockObject.h */; settings = {ATTRIBUTES = (Project, ); }; };
0D535DEFAB38F82F523FA2970DDE9A88 /* OCMNotificationPoster.h in Headers */ = {isa = PBXBuildFile; fileRef = B5D03644E567590DEDC2D0AE3425D98D /* OCMNotificationPoster.h */; settings = {ATTRIBUTES = (Project, ); }; };
0DB61F661491104AF5F8778F810C37D4 /* RACSubscriber.m in Sources */ = {isa = PBXBuildFile; fileRef = 1291349B3C35F79CB6F631FD1CDCC1B0 /* RACSubscriber.m */; };
0DF1C43C7B27B093F8BDF7B951233D19 /* TyphoonSelector.m in Sources */ = {isa = PBXBuildFile; fileRef = 95E34BD0B0987191F26A0B35F90CF6E5 /* TyphoonSelector.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
0E391D08B6652D83393D3E86C70B9BEB /* TyphoonInjectionByObjectInstance.m in Sources */ = {isa = PBXBuildFile; fileRef = 67345DA89FFA1B9FC73EA47478CB886D /* TyphoonInjectionByObjectInstance.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
0E6C53D91EE72B414A29D19423842652 /* OCMRecorder.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F18C60D29A81CE3566D4A38800979FA /* OCMRecorder.h */; settings = {ATTRIBUTES = (Public, ); }; };
0E8A6DC318277807A45CB21C40672C3F /* RACSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BCF3C4AEB69B5B155617E83726EB0A0 /* RACSignal.m */; };
0EE4D51D6E6CB670272FED77C64D87EE /* OCObserverMockObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 8F54C727953E14DD511A46A9F90E8937 /* OCObserverMockObject.h */; settings = {ATTRIBUTES = (Project, ); }; };
0F0B53B1D332733C46244D44DAB58508 /* TyphoonColorConversionUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 42BB4F99181287983D693A457D56DE96 /* TyphoonColorConversionUtils.h */; settings = {ATTRIBUTES = (Public, ); }; };
0F1D0F5DCC8C94A4C684DF846D14F436 /* SDWebImagePrefetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = E14AF6E304C6098B7A146E1BAFC26C6C /* SDWebImagePrefetcher.m */; };
0F307CCAE2D45876CCD0983763D5D45B /* RACEmptySignal.h in Headers */ = {isa = PBXBuildFile; fileRef = D8706CB35453DAA97FE6E56A07BF5E79 /* RACEmptySignal.h */; settings = {ATTRIBUTES = (Private, ); }; };
0F5ACCF4A4F36FAC65E045A9F87E3003 /* TyphoonInjectionByObjectFromString.m in Sources */ = {isa = PBXBuildFile; fileRef = 419694111A274B662810AE1FECA72F35 /* TyphoonInjectionByObjectFromString.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
0F76408EFB678D11DB62752B64017A40 /* TyphoonPropertyInjection.h in Headers */ = {isa = PBXBuildFile; fileRef = F3F7B76572B953FCBB908A0E7605EB53 /* TyphoonPropertyInjection.h */; settings = {ATTRIBUTES = (Public, ); }; };
0FF9F459ED16719292443A4C99B52B20 /* SDImageCache.m in Sources */ = {isa = PBXBuildFile; fileRef = E597546B5AFBBDBBDE17E78E1E1EC111 /* SDImageCache.m */; };
0FFA63B0103710B3ACDAE72B33A73200 /* NSInvocation+OCMAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = D66DFDCCBA63EB04C097EA2E0C475EE5 /* NSInvocation+OCMAdditions.h */; settings = {ATTRIBUTES = (Project, ); }; };
10017B43AC38C3A89D7AC1376C6E7066 /* SDImageLoadersManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 0851ED90E0FAF2464FCE1AB36209BB60 /* SDImageLoadersManager.h */; settings = {ATTRIBUTES = (Public, ); }; };
1046610A51620E727140217FEC6DD5BF /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D376F0D7BDE2FCC4DDC6743AFC8DDA9E /* Foundation.framework */; };
10B1F7A3301C951B9F48B9B8F0CB1B31 /* Pods-iOS-Network-Stack-DiveTests-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 1051F60A0834845519249BBEEB7669EF /* Pods-iOS-Network-Stack-DiveTests-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
10F5D29724DC5FC71BFB33A0B6EB3D6B /* TyphoonInjectionByRuntimeArgument.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E607FCD32089EA9D313F72664F0B17B /* TyphoonInjectionByRuntimeArgument.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
110880E9AFA349C846476980F8AF8086 /* TyphoonTypeConversionUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 06B502FEF6AC1E2D1DECEF63D08CC04F /* TyphoonTypeConversionUtils.h */; settings = {ATTRIBUTES = (Public, ); }; };
11422CB222C72A6EF77DA96D152FA6F8 /* UIBarButtonItem+RACCommandSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 63342C5E18062111D90810C1BF11878B /* UIBarButtonItem+RACCommandSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
127034388C945656BD5A7C979DA4029E /* TyphoonInjectionByCurrentRuntimeArguments.h in Headers */ = {isa = PBXBuildFile; fileRef = C0DFA61DA615C88D4F562DF9D047B134 /* TyphoonInjectionByCurrentRuntimeArguments.h */; settings = {ATTRIBUTES = (Public, ); }; };
12A631453F473C22F8936BF263BAD23E /* TyphoonJsonStyleConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 821F3BAC1C291831ECD436462BB57063 /* TyphoonJsonStyleConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; };
12A67ABB2A4FE1BCF978075EBC33EB90 /* TyphoonInstancePostProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = 1061DB1631D08DF649DC362D1104033F /* TyphoonInstancePostProcessor.h */; settings = {ATTRIBUTES = (Public, ); }; };
12BE89AC12766C6CE239FD4EA3543599 /* UIView+TyphoonOutletTransfer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8065BF1B5F4AFDB278D6AB4C732153BE /* UIView+TyphoonOutletTransfer.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
12DEDFBDD6A19E45734C9BF576DB4379 /* RACSerialDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = 26BFC051219DBE2ECE65E6DD31A177F9 /* RACSerialDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; };
133E972891174F621EB1C605A111C61E /* TyphoonComponentFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 2EE6EC22E733D815C1F5084910F7491B /* TyphoonComponentFactory.h */; settings = {ATTRIBUTES = (Public, ); }; };
138463D749696B86F3324AC0882A64EB /* GCDAsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A89A507382B833489D6DB8D75E6DE2 /* GCDAsyncSocket.m */; };
1444DFD1492DECA5C8C0760EF0FB6CA6 /* NSDictionary+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 7CE2A8DCEE78DE1933B81C994103BE63 /* NSDictionary+RACSequenceAdditions.m */; };
14676CDFFDE06FF960179AA34C474EEE /* RACStringSequence.h in Headers */ = {isa = PBXBuildFile; fileRef = 8A14CECA21E108EE12451203537A6B99 /* RACStringSequence.h */; settings = {ATTRIBUTES = (Public, ); }; };
147A5463FF6FFE40426B898B60344085 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5EFE23AB30614DC2B36B61715A6990FD /* CFNetwork.framework */; };
14CA284AC4FF1EED75E785641EE98034 /* SDImageCacheConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = 8AC5ECC34DB25CB5305086B539A17417 /* SDImageCacheConfig.h */; settings = {ATTRIBUTES = (Public, ); }; };
150373979C0DF8F34C788F000C2BE265 /* TyphoonInjectionByConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = 765309DEC91CA062A4172B75C508325C /* TyphoonInjectionByConfig.h */; settings = {ATTRIBUTES = (Public, ); }; };
15B27182B591769C57B55544260DC886 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D376F0D7BDE2FCC4DDC6743AFC8DDA9E /* Foundation.framework */; };
165F1C9CBD621828C788A3018D0426C5 /* SDImageAPNGCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = BD5A68EEDD637AF3394D147538906789 /* SDImageAPNGCoder.h */; settings = {ATTRIBUTES = (Public, ); }; };
16D7DCB7CC985C33EEC41B371C029C84 /* SDWebImage-SDWebImage in Resources */ = {isa = PBXBuildFile; fileRef = CF1281E58AA1045D4B7F33FC56691C42 /* SDWebImage-SDWebImage */; };
1708C1D28B421C4AD310426D1695CE77 /* SDAnimatedImage.m in Sources */ = {isa = PBXBuildFile; fileRef = E5F9720FC7B55FFB33E76C326A7DDE5E /* SDAnimatedImage.m */; };
17134C3258CDB162F0507654B177CAE3 /* RACUnit.h in Headers */ = {isa = PBXBuildFile; fileRef = B28FA990E304B0E57E1599F3DDAC5301 /* RACUnit.h */; settings = {ATTRIBUTES = (Public, ); }; };
1725041850D30E89E549FD343C9B1073 /* NSMethodSignature+TCFUnwrapValues.h in Headers */ = {isa = PBXBuildFile; fileRef = CAE4756369959CF3EB303367CDDEBAFD /* NSMethodSignature+TCFUnwrapValues.h */; settings = {ATTRIBUTES = (Public, ); }; };
1754DD5511A7BF462B116F70B0D4006A /* SDWebImageOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 0F47471F1705425CC58FB3536B619557 /* SDWebImageOperation.m */; };
179413BD936A98D571223DDA39AB9786 /* UIActionSheet+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 42AC4C9E32B2DF0DF2366BB6D08859AE /* UIActionSheet+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
17A4935F78766E41FC29D1D668146EF3 /* RACTupleSequence.h in Headers */ = {isa = PBXBuildFile; fileRef = 21F7FF6AAD9960A7E34FA700AF4A2F1D /* RACTupleSequence.h */; settings = {ATTRIBUTES = (Public, ); }; };
17D77D58509A819B927EFD3454361B4D /* NSNotificationCenter+OCMAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 06E2166895FA1466EACF8506DAA383DB /* NSNotificationCenter+OCMAdditions.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
1830558A4D2D63C8E76BC3136D8213F9 /* UIImage+ExtendedCacheData.h in Headers */ = {isa = PBXBuildFile; fileRef = 909F75C790C08B1A24A71FF138EA5C7B /* UIImage+ExtendedCacheData.h */; settings = {ATTRIBUTES = (Public, ); }; };
1839437DDEA6EB16A5239F19484FDC2F /* NSIndexSet+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 14752B57BED25D3E2474FA3AFEB5ECD7 /* NSIndexSet+RACSequenceAdditions.m */; };
18660FA595DBE133BB784E813A7122A8 /* SDImageHEICCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 588908A9A5F504D890248C42BDD5B35C /* SDImageHEICCoder.m */; };
18AD90784D549657DF51BC8377DA3085 /* SDWebImageDownloaderResponseModifier.h in Headers */ = {isa = PBXBuildFile; fileRef = BAFC1AB1A830CD29F44C74C0B71EF450 /* SDWebImageDownloaderResponseModifier.h */; settings = {ATTRIBUTES = (Public, ); }; };
196AAABF9ECA51DE0971598C883B4834 /* TyphoonNibLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = B49E72E762657E3284FA9D7A026A49E6 /* TyphoonNibLoader.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
19AC17E22E91595F6E0D5FF22AF7E834 /* TyphoonCollaboratingAssembliesCollector.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D69D7A45A56F3986C52DF0088F96C35 /* TyphoonCollaboratingAssembliesCollector.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
19B35F7B7E03A4884DF2861528AA40E1 /* TyphoonTypeDescriptor.h in Headers */ = {isa = PBXBuildFile; fileRef = E87C4CECC849A38798AC17F0D2EDBDC7 /* TyphoonTypeDescriptor.h */; settings = {ATTRIBUTES = (Public, ); }; };
1A51CF9668347C9AB7F0DA8A9D149399 /* RACSerialDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BB3D4EE11AB02C35A56E26CD538EA8D /* RACSerialDisposable.m */; };
1A5A36F12519F4D3B9A2502D3A733381 /* Typhoon-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BE05A31C373E76C2393361CF1E8AF9C /* Typhoon-dummy.m */; };
1A8F4D7FCCFAA0A692DB10B31D3C4A70 /* UITableViewHeaderFooterView+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 744D46F0AC450056AAE46AA71A878857 /* UITableViewHeaderFooterView+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
1B6CE67196EE181E6B56788EFC7E00D3 /* SDImageGIFCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 0AE0533E46155704FC6E99D900ECFF97 /* SDImageGIFCoder.h */; settings = {ATTRIBUTES = (Public, ); }; };
1BC44E2FDD197D5210A23C9CCF1A906B /* SDWebImageCompat.m in Sources */ = {isa = PBXBuildFile; fileRef = 198733CC5474A54E6311C06F7EF78E54 /* SDWebImageCompat.m */; };
1BE3F2879E492B7F9A5AEBA3E3766100 /* NSDictionary+CustomInjection.h in Headers */ = {isa = PBXBuildFile; fileRef = 12EC94936DE4F7A5029D489A3E4FB32E /* NSDictionary+CustomInjection.h */; settings = {ATTRIBUTES = (Public, ); }; };
1C6A9BE75AF46B08D04DEBB8DA4D687A /* NSObject+RACSelectorSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = 8386D6BB6CF3F2D21ED967CC288819B0 /* NSObject+RACSelectorSignal.m */; };
1C8B70C74291A3076746C3B18781568E /* SDImageCachesManagerOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 91A1F2245A7DAF44FC87AADAFA9E9BA9 /* SDImageCachesManagerOperation.h */; settings = {ATTRIBUTES = (Private, ); }; };
1CB52E450D48ACF5A2F1232D03755A1D /* TyphoonInjectionByCurrentRuntimeArguments.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A273691556F81E2F190244D0347ED31 /* TyphoonInjectionByCurrentRuntimeArguments.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
1CCD65F93AB302520C82949625A48CE7 /* TyphoonInjections.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F3AD4FDCF5DACA1727D2EDD1CDAA03F /* TyphoonInjections.h */; settings = {ATTRIBUTES = (Public, ); }; };
1CDFABA9B2A06A4238E706017BE1F38A /* RACScopedDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = 5A66CEA5D0C3EE038662C0D25CA04F50 /* RACScopedDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; };
1E21419F413F6D4CA470254D9A0349BA /* TyphoonViewControllerNibResolver.h in Headers */ = {isa = PBXBuildFile; fileRef = 8732F0D1FA8AA62E703DE7973280BA55 /* TyphoonViewControllerNibResolver.h */; settings = {ATTRIBUTES = (Public, ); }; };
1E23DAF8BFF220AA548435F8987B5EC8 /* TyphoonAssemblyAdviser.h in Headers */ = {isa = PBXBuildFile; fileRef = 37E635EECAF9EE9AFD5F838C93C42334 /* TyphoonAssemblyAdviser.h */; settings = {ATTRIBUTES = (Public, ); }; };
1E4A2A24D1FA36114E4A37633FC546D3 /* NSObject+DeallocNotification.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C7C6F0CA41AC6FA5A0EAD49FADBE0DE /* NSObject+DeallocNotification.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0 -DOS_OBJECT_USE_OBJC=0"; }; };
1F255700661347843AD0580B1AA7D6A5 /* RACScheduler+Subclass.h in Headers */ = {isa = PBXBuildFile; fileRef = 183FD7641AE552C24F25EC96C491FCBF /* RACScheduler+Subclass.h */; settings = {ATTRIBUTES = (Public, ); }; };
1F9CDA98F3994B1BE2B535BDF2D073E5 /* Pods-iOS-Network-Stack-Dive-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E1DD37617D3B108328AB936760E6BDD /* Pods-iOS-Network-Stack-Dive-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
1FD7A52353875571B01C3E7FD9845744 /* OCMConstraint.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A12D240C381406917C3EFB576E33710 /* OCMConstraint.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
20199A7A56B3DF111724477B175701D0 /* Pods-iOS-Network-Stack-DiveTests-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = E526E2D4A9BDEA51AAF7B2A5AC6A46FA /* Pods-iOS-Network-Stack-DiveTests-dummy.m */; };
207791430D1E92DF86EA8E7CE685BE30 /* RACSignalSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = 5354038C92F8485F19D1D15D3BF5FFFC /* RACSignalSequence.m */; };
20D618EF3EA5E3BE96DA24D36E3CA9EF /* SDAsyncBlockOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 50B9C0A664812C3E14A3224A605C9402 /* SDAsyncBlockOperation.h */; settings = {ATTRIBUTES = (Private, ); }; };
215534F06D3902AE9E7D62DC1718C568 /* TyphoonConfigPostProcessor+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B16EB1B90C8121FC99C6D634CE916AA /* TyphoonConfigPostProcessor+Internal.h */; settings = {ATTRIBUTES = (Public, ); }; };
2226813C530D9A755E29C9F35308E33C /* TyphoonTestUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = EA1F7903B377E9FFA5C2028618BA7439 /* TyphoonTestUtils.h */; settings = {ATTRIBUTES = (Public, ); }; };
225F924255C41D53958FEB795EB51FDE /* RACmetamacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 950CD6BE6707ED534FF448D9CC40FA3D /* RACmetamacros.h */; settings = {ATTRIBUTES = (Public, ); }; };
228CDD92F991FA023D6F06B69327A028 /* OCMInvocationMatcher.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DB0A4F8CE90B57FB6935727ABCA7CE2 /* OCMInvocationMatcher.h */; settings = {ATTRIBUTES = (Project, ); }; };
22C00E6FDBCBBEB33AE6CDC8DFA60571 /* TyphoonPrimitiveTypeConverter.m in Sources */ = {isa = PBXBuildFile; fileRef = 620115CAB08E9FF9898971DB60F30BA6 /* TyphoonPrimitiveTypeConverter.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
22F72E872A783A8AB4670B5037787DD6 /* NSArray+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 43154AF288B8222D79172AC422866E6C /* NSArray+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
244415898475C361F53B52BC1A34407E /* RACKVOProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 856CE023A29559B385271D8014E85BC3 /* RACKVOProxy.m */; };
2483B32E63F6D95EFD66313968EDE3E7 /* TyphoonInjectionByRuntimeArgument.h in Headers */ = {isa = PBXBuildFile; fileRef = 6C1ADE2AAE0ED64E1AC780F3A8C2B911 /* TyphoonInjectionByRuntimeArgument.h */; settings = {ATTRIBUTES = (Public, ); }; };
24D5DB6DC75B9301C8A56C067549ED18 /* TyphoonLinkerCategoryBugFix.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B7043029F68EFD2C4174A964B0551C6 /* TyphoonLinkerCategoryBugFix.h */; settings = {ATTRIBUTES = (Public, ); }; };
24E8E4ED0B5D988E3346E6638619F4E4 /* SDImageFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CD76B64F3F84AD0EA28EDA5BE8A26BE /* SDImageFrame.m */; };
255702D1853E23E30B6847F6A26402F9 /* RACBlockTrampoline.m in Sources */ = {isa = PBXBuildFile; fileRef = E37027B951A73179B2F4337FF41422D7 /* RACBlockTrampoline.m */; };
259D1784222B56F16D06111003ABD61C /* TyphoonCircularDependencyTerminator.m in Sources */ = {isa = PBXBuildFile; fileRef = 04CD8685467C1425C560A1E9C0AA43B7 /* TyphoonCircularDependencyTerminator.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
25C3A2BA3CEF4D94C5C34CD8FE3FFAE2 /* UIButton+RACCommandSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 6499637B2B14B146BF05E82D9F524DB0 /* UIButton+RACCommandSupport.m */; };
2609AEF9B7C24A507DAE3AA17E8ECA21 /* RACSubscriptionScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = 2E0DEBA6FAF64625746D89A660E075B9 /* RACSubscriptionScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; };
27DAE8D358BD60E2886684E5A056E752 /* TyphoonDefinition+Config.h in Headers */ = {isa = PBXBuildFile; fileRef = 48F2519B307B02B43FC5B68399AF554F /* TyphoonDefinition+Config.h */; settings = {ATTRIBUTES = (Public, ); }; };
28011D6203A46798D789585BB2F5B7F9 /* RACImmediateScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = 1443B87E860FFFC4733D9B16073E19F9 /* RACImmediateScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; };
288D796F3F7B9F42690E24A3B1018B2C /* SDImageIOAnimatedCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 15DBB333129764191B36CFF037F62681 /* SDImageIOAnimatedCoder.m */; };
28C71D0DD2723BBE5D5C74A64FE185A0 /* RACReturnSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD56F486BA750FFE7B13856AC85FEA2 /* RACReturnSignal.h */; settings = {ATTRIBUTES = (Public, ); }; };
29489009B707CF4D904BEF00214A77C5 /* RACScheduler+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B2C8AD692D98D47C5658A9FE6A6DE4F /* RACScheduler+Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
29939A199EE4BAE8976AEC88E59F2ABB /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C6D6E31E7C7B4D803BA05555FE0FF607 /* CoreFoundation.framework */; };
299D0A26A874ADE845CBB1654CFB48EE /* TyphoonBlockDefinition+InstanceBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = 955DB0048C92F2F02195AF9A7C000F04 /* TyphoonBlockDefinition+InstanceBuilder.h */; settings = {ATTRIBUTES = (Public, ); }; };
29F7F0E98FD26A96364DBACD7D5F237A /* SDWebImageDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D1458925F5D672BF44A88E619165414 /* SDWebImageDownloader.h */; settings = {ATTRIBUTES = (Public, ); }; };
2A71217971D20B8AB2B3E50CC364417E /* TyphoonViewHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 02DBA7718F60FA5F5208D3C92DD2FAE1 /* TyphoonViewHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; };
2A7799AF29FD31F1C0EAC4857E7511F2 /* TyphoonCallStack.h in Headers */ = {isa = PBXBuildFile; fileRef = 8F0A29B88BD1DA370225345A593E408F /* TyphoonCallStack.h */; settings = {ATTRIBUTES = (Public, ); }; };
2AB2A962A952CBC6F38DE2938E055B0E /* OCMMacroState.h in Headers */ = {isa = PBXBuildFile; fileRef = 7E53BFD7942CD993EE50F7FD12C87B2C /* OCMMacroState.h */; settings = {ATTRIBUTES = (Public, ); }; };
2AD6EE04D5EC7568C100BEE596D4C545 /* NSArray+TyphoonManualEnumeration.m in Sources */ = {isa = PBXBuildFile; fileRef = 3CB4F2D27F00A3C400E970AB3C7AB6BC /* NSArray+TyphoonManualEnumeration.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
2B05197D9780DA9C2A6C74663FDC1BE8 /* UIButton+RACCommandSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = B05C75D2995DD598CD05DC64D113A11E /* UIButton+RACCommandSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
2B0D8ABFCA7A2A99F4E3A2F35820DAAA /* TyphoonDefinitionNamespace.m in Sources */ = {isa = PBXBuildFile; fileRef = A5A8D22D51747CC57261A39513F63FBF /* TyphoonDefinitionNamespace.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
2B7DFF21453E38A19B45F8E11EF278E3 /* OCMLocation.h in Headers */ = {isa = PBXBuildFile; fileRef = FCD6A66B2012CDBD25E31AE588E07483 /* OCMLocation.h */; settings = {ATTRIBUTES = (Public, ); }; };
2BEDD35426B348EC616C6A0939C6BD83 /* RACAnnotations.h in Headers */ = {isa = PBXBuildFile; fileRef = E49D958DD1C460DCA691F17881DE66D9 /* RACAnnotations.h */; settings = {ATTRIBUTES = (Public, ); }; };
2CFFDBFE764BA3B8B0B878BC3A0E7083 /* TyphoonStartup.m in Sources */ = {isa = PBXBuildFile; fileRef = DB337B4965F7DD6CACD10EBA48209055 /* TyphoonStartup.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
2D12D093F3425B423613BB39DC40A72A /* OCMFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C26E7915BAEE3BDE02BCD9D2D389B9A /* OCMFunctions.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
2D873076AFF986FB88A07B056E6E1B45 /* UISwitch+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 9AE77ABB8D49E71EE7F0CFAFEC9710A4 /* UISwitch+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
2DB0021F9583F06D433E0EA01CC4BD65 /* YYClassInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 33C36535BCF88C0F8CE4BEE165483F16 /* YYClassInfo.m */; };
2DD65DE2BC1D3B6536ECC06F2BB1CB26 /* RACEmptySignal.m in Sources */ = {isa = PBXBuildFile; fileRef = 2604998C21DB77EB38BB124802109F52 /* RACEmptySignal.m */; };
2DDD48230ED9E8068C7E439D79B99A8E /* SDInternalMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 000F69D9699B233904DC78740C74688D /* SDInternalMacros.h */; settings = {ATTRIBUTES = (Private, ); }; };
2EC9A7EDF03D4C66CB4DA9F65E1AC35B /* OCMInvocationStub.m in Sources */ = {isa = PBXBuildFile; fileRef = DF3E60E69433B185D69240ECF94CFCFB /* OCMInvocationStub.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
2EFF1EFA2FF0D2F1AE44A9DDBB932425 /* UIScrollView+EmptyDataSet.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F40215065345DB127D2130E4F8AA842 /* UIScrollView+EmptyDataSet.h */; settings = {ATTRIBUTES = (Public, ); }; };
2F6D9BEA582A2DBB70A6C3B2FC2DB91E /* SDWebImageDownloaderResponseModifier.m in Sources */ = {isa = PBXBuildFile; fileRef = 40F0A65B2C96B6E67B7417305F598D6D /* SDWebImageDownloaderResponseModifier.m */; };
2FA3D210CCCBA41BC36F0D716F173F19 /* NSInvocation+TCFUnwrapValues.h in Headers */ = {isa = PBXBuildFile; fileRef = 4C07DF0C9AD7EAEC76E6F3D297E58F66 /* NSInvocation+TCFUnwrapValues.h */; settings = {ATTRIBUTES = (Public, ); }; };
2FDCF9468E03C9B56AB0F7740669B4F5 /* NSValue+TCFUnwrapValues.m in Sources */ = {isa = PBXBuildFile; fileRef = DE4C0740ECC01D6ED27D9ABD7EA5172B /* NSValue+TCFUnwrapValues.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
2FEAE6F6465F609BC48C5F020306A6C5 /* Collections+CustomInjection.m in Sources */ = {isa = PBXBuildFile; fileRef = 8CCB0069A6BAC8CD752488DF7B7D42B7 /* Collections+CustomInjection.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
3025FD4C72722EDAA7B5778B22735B62 /* OCMStubRecorder.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C3F45C3BA62C5E06E78277D0D81E73D /* OCMStubRecorder.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
30FE660831E11D2EA5791BB9D84A3978 /* UITableViewCell+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = FE568D9819827B0D7AC039F020E84BB1 /* UITableViewCell+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
317B814C899E76C4C794A92F42748AB5 /* NSObject+TyphoonIntrospectionUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = EAFD5F2E394EE2C4144D87785849CD68 /* NSObject+TyphoonIntrospectionUtils.h */; settings = {ATTRIBUTES = (Public, ); }; };
3187FF0C251D1B78BE87F64F6F6E944A /* SDWebImageTransition.m in Sources */ = {isa = PBXBuildFile; fileRef = 7333039A34CFB7AC3CD117DC76BF5999 /* SDWebImageTransition.m */; };
31C05D64D42CA917CEF5585183DD47B3 /* OCMInvocationExpectation.h in Headers */ = {isa = PBXBuildFile; fileRef = D400046FCD67C3ECE87A4FD7570DF9B7 /* OCMInvocationExpectation.h */; settings = {ATTRIBUTES = (Project, ); }; };
31DC2EC78AD1F8241AE6051EF9E73B0A /* SDWebImageDefine.m in Sources */ = {isa = PBXBuildFile; fileRef = F2C354A6FCF564B6B35C09D98D9B276E /* SDWebImageDefine.m */; };
31E03594824B9C1868700CB719A49612 /* NSUserDefaults+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 41BDCD9D2400DA765DCB894C8509679B /* NSUserDefaults+RACSupport.m */; };
320DE42AF3CFE11FF785FEB1A7E6547B /* SDImageFramePool.m in Sources */ = {isa = PBXBuildFile; fileRef = C5EE4FBC26F92ABB4464D8BED481D33A /* SDImageFramePool.m */; };
32ACEDCEBE0507A82D6323114A1C74F1 /* UIImageView+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = E1DC9A972C497BFFC7B688A24BA28E98 /* UIImageView+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
32F2B91621A2F8F9AD7C8E2B224D73F6 /* SDWebImageDownloaderDecryptor.m in Sources */ = {isa = PBXBuildFile; fileRef = 6390D1A8353BED0692FDB19AF76851C0 /* SDWebImageDownloaderDecryptor.m */; };
33BE7CF4354A6753F1CAB84BEC9F0575 /* NSMethodSignature+TCFUnwrapValues.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C982E20E08BC60740455993A35E8A3F /* NSMethodSignature+TCFUnwrapValues.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
33D3587AF629B2FA21554DA002D6ACB8 /* SDImageCachesManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1343632E0E45C40E0DBA77566514A62A /* SDImageCachesManager.m */; };
3443028D46C9F3EF2BD8A7A22B4DC101 /* TyphoonStackElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D40DD7E6EAE2E8222553A0E28A69834 /* TyphoonStackElement.h */; settings = {ATTRIBUTES = (Public, ); }; };
34519026CD19276CF7FA8A6921FBD3A7 /* OCMFunctions.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EB0DF190C36C4090D74F64BB54053A6 /* OCMFunctions.h */; settings = {ATTRIBUTES = (Public, ); }; };
34AAA32786D16B879ED317280AD7C550 /* RACStream+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 8090506D09D6280408FEFDC474C367CF /* RACStream+Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
34B28D4F0168194B6EFAC0520EB7A7F4 /* NSImage+Compatibility.h in Headers */ = {isa = PBXBuildFile; fileRef = EE05E240495D85D96BA4CB6CB3310F63 /* NSImage+Compatibility.h */; settings = {ATTRIBUTES = (Public, ); }; };
34C31624E93FB93E37E4C25D7E252A6A /* OCMVerifier.h in Headers */ = {isa = PBXBuildFile; fileRef = 3C12837A69C57039BE360EAF15FE6C76 /* OCMVerifier.h */; settings = {ATTRIBUTES = (Public, ); }; };
34F0AC42EC0E17A78D8B472363FA732D /* NSSet+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = E369629E03DEAACC1711DB17684549A9 /* NSSet+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
35F8F16617EA4D58688C0633DF1ED887 /* UISwitch+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 83901A74DF9DB0A0733D4591727B3770 /* UISwitch+RACSignalSupport.m */; };
3650C86E60D03E4678315B00B3910572 /* UIImagePickerController+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 2CD5CB6BE4742004BAEA7654CE3C6AF7 /* UIImagePickerController+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
36F4B09E7C71DCC5CEC6057814033C37 /* UIView+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = B0783913DE452E225655A280A31FEFA8 /* UIView+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
3777CD89D444CBBB48AE323B303F3FC7 /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E215B27470E9F7E028D285C07B15B9B /* ImageIO.framework */; };
37B890ABDC7DD441E6AA662325D412E6 /* MASConstraint.h in Headers */ = {isa = PBXBuildFile; fileRef = 9630797851476E7C030A663FB01C053D /* MASConstraint.h */; settings = {ATTRIBUTES = (Public, ); }; };
38007D8B2D7B055D75C486183F0EADF3 /* RACScopedDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = A64869131A94BAF78EB608FAED187F90 /* RACScopedDisposable.m */; };
385FEBB3BFA25B1AA8ECDFF703FD997A /* TyphoonComponentsPool.h in Headers */ = {isa = PBXBuildFile; fileRef = 65677A776ECF0D5458AAFDDA49ADDFC4 /* TyphoonComponentsPool.h */; settings = {ATTRIBUTES = (Public, ); }; };
38938E604A7D708E6378A44063EF3512 /* UIImageView+WebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = A09A92A8038E8620B3B2A61D8A929180 /* UIImageView+WebCache.m */; };
392AC16CCBF2A50096F0B3303E13ADA7 /* NSNotificationCenter+OCMAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = F89876C64AB0B6C50E8EE3A5E905CBB2 /* NSNotificationCenter+OCMAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
392E6367FB1A10EEA969ED00E460E4DD /* NSFileHandle+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 795FD8B2FD7ED943A340C32633ED18F7 /* NSFileHandle+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
3A1AD84C0DC3C256418CC46739024E96 /* SDmetamacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 54AA8A2F8E3322A66F3449823D3B76B5 /* SDmetamacros.h */; settings = {ATTRIBUTES = (Private, ); }; };
3ACE99697BE48783D60907A57A5F2FF1 /* TyphoonStoryboardDefinitionContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D5383F6C03BB8925217003BD9A3A82C /* TyphoonStoryboardDefinitionContext.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
3AECFF92CB2DDFE798511330B5E8D704 /* OCMExpectationRecorder.m in Sources */ = {isa = PBXBuildFile; fileRef = 437B0AD6B69A2013C65A324A3CBBF3CA /* OCMExpectationRecorder.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
3B46F3921BE0E2CCB23E716DD6D41D6D /* OCMArgAction.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DA3CE4677195B9C5B8F50C6E3B91D81 /* OCMArgAction.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
3B55DB273C7CD31AACE379298843C0A1 /* UIView+TyphoonDefinitionKey.h in Headers */ = {isa = PBXBuildFile; fileRef = 8D713D070421A1E803471AA59CAE1B25 /* UIView+TyphoonDefinitionKey.h */; settings = {ATTRIBUTES = (Public, ); }; };
3B959E6505C82CA0FF804E07CA51492A /* NSEnumerator+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 74E20E5BB34220E9282F27E20A9A567E /* NSEnumerator+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
3BFA92648AC1313CFD040750ACE5D0C1 /* RACSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = 793AD3A27E2A7A7D6570D1D0B8E748CF /* RACSequence.m */; };
3C0A1F9E0F5FDD72150F8A21A4672935 /* OCMQuantifier.m in Sources */ = {isa = PBXBuildFile; fileRef = E59E49F550CAE1C1C8665F6CCF893735 /* OCMQuantifier.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
3C2E1F3AF1E008F5610EDBB2258CA617 /* RACSignal+Operations.m in Sources */ = {isa = PBXBuildFile; fileRef = 55EB526211489C964AE450F84E636291 /* RACSignal+Operations.m */; };
3C76848E9AA1EE9FB3CA1BAD6A217790 /* RACSubscriber.h in Headers */ = {isa = PBXBuildFile; fileRef = 143606750438523F029FB6DA44890C07 /* RACSubscriber.h */; settings = {ATTRIBUTES = (Public, ); }; };
3C7815EEC599DD7D42FDEF19B2FF1563 /* SDWebImageOptionsProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = 933EF5CE0D7C9A91603A2F91BDFD0871 /* SDWebImageOptionsProcessor.h */; settings = {ATTRIBUTES = (Public, ); }; };
3C7EAECB8C573E714C818BA04EB33773 /* UIImage+MultiFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = CEB064A038D098A7FFF817FFA5B9F340 /* UIImage+MultiFormat.h */; settings = {ATTRIBUTES = (Public, ); }; };
3C8F2F868D0C361CAF43E53CDB8EB631 /* SDWebImageCacheSerializer.h in Headers */ = {isa = PBXBuildFile; fileRef = D4142DA4BEFA74E0A9968C239127FF24 /* SDWebImageCacheSerializer.h */; settings = {ATTRIBUTES = (Public, ); }; };
3CB27E5F83DADA61D461099B00ED2895 /* OCMock.h in Headers */ = {isa = PBXBuildFile; fileRef = 4987ACD1FED6CF90E9AE135E75E5AE57 /* OCMock.h */; settings = {ATTRIBUTES = (Public, ); }; };
3D0BBFEC1921CE71BC240DC18D8BE540 /* SDImageTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 073BF9A27C7F924FC6A5CF2E935DDDA8 /* SDImageTransformer.m */; };
3D81A472030665B9BA7F4BF2E11A4541 /* TyphoonCollaboratingAssembliesCollector.h in Headers */ = {isa = PBXBuildFile; fileRef = F655091C80A455BB5F06459C5CC0EC71 /* TyphoonCollaboratingAssembliesCollector.h */; settings = {ATTRIBUTES = (Public, ); }; };
3D98D6C75AE8D46FEF6F5414F50815B2 /* OCMObjectReturnValueProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = A840AE48AD863CF06B48FE55C745EA9D /* OCMObjectReturnValueProvider.h */; settings = {ATTRIBUTES = (Project, ); }; };
3DEB0926F884FA92604800641EF8BAC6 /* NSMethodSignature+OCMAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE925C4BC3366755D6B62640CCCFCAE /* NSMethodSignature+OCMAdditions.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
3E1D9FD4E15EAD00141A7A1342A0CC10 /* RACDynamicSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F5DE94BB623F6C7A33F0A746E8F58DB /* RACDynamicSignal.h */; settings = {ATTRIBUTES = (Public, ); }; };
3E983180E84D6AC282EF25CDC8368D41 /* OCMIndirectReturnValueProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 2EEE06C505E1457D30A4ACDA3820D377 /* OCMIndirectReturnValueProvider.h */; settings = {ATTRIBUTES = (Project, ); }; };
3F9C3275F786E754C812482941E96F4A /* RACEXTRuntimeExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F26AA73AA98E00F8C916633F02D50F8 /* RACEXTRuntimeExtensions.h */; settings = {ATTRIBUTES = (Private, ); }; };
3FF9873BBEDD1C8AA6EC43EBCB23D060 /* NSData+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CEAF4D7D00B3A496611716400E24A3C /* NSData+RACSupport.m */; };
40613D5600FE76DC9F8F377846577314 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D376F0D7BDE2FCC4DDC6743AFC8DDA9E /* Foundation.framework */; };
416DA8B2997381F954DBA6E6A53DA4A2 /* NSData+ImageContentType.m in Sources */ = {isa = PBXBuildFile; fileRef = ECA70F67E1A45D6DB171A53CF98C8090 /* NSData+ImageContentType.m */; };
422523173C30F2F9695A6635431DAF64 /* TyphooniOS.h in Headers */ = {isa = PBXBuildFile; fileRef = BD5D5509B774B3EA20B4EBDBD246DD8C /* TyphooniOS.h */; settings = {ATTRIBUTES = (Public, ); }; };
425C9EA28FBEB7F7FC09A3F4A88C5955 /* SDWebImageError.m in Sources */ = {isa = PBXBuildFile; fileRef = ABBE463A361CCED6D1E02450518F512E /* SDWebImageError.m */; };
42735B679585CBE859585C298BB91FC6 /* UIControl+RACSignalSupportPrivate.m in Sources */ = {isa = PBXBuildFile; fileRef = 93894FEF634076038AE57F0DA89A230A /* UIControl+RACSignalSupportPrivate.m */; };
42DC6F6F01A9526A6A9687A6279E928B /* TyphoonCollaboratingAssemblyPropertyEnumerator.m in Sources */ = {isa = PBXBuildFile; fileRef = 62B6BED9AA97ED0F595CFB0D93C898E2 /* TyphoonCollaboratingAssemblyPropertyEnumerator.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
436764D67026FF2C796BC826CEC803DD /* OCMExceptionReturnValueProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = E8561AC4257A4B8D0B0569EEEA2E4555 /* OCMExceptionReturnValueProvider.h */; settings = {ATTRIBUTES = (Project, ); }; };
43BC3B34407AEA67381BAD180BCA1156 /* TyphoonPatcher.h in Headers */ = {isa = PBXBuildFile; fileRef = 2CD41B1FA71FB0AE97EB624502ACF0A1 /* TyphoonPatcher.h */; settings = {ATTRIBUTES = (Public, ); }; };
445C3272BC1602F3CF1B140BC2A0B39C /* UIBarButtonItem+RACCommandSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DA14F675C32DB069C21840DFF0D5B75 /* UIBarButtonItem+RACCommandSupport.m */; };
44AEABDD5FE11DCE9EE1F00ECBC340F5 /* TyphoonStoryboardDefinition.h in Headers */ = {isa = PBXBuildFile; fileRef = 8D91285C522F06C05B537D395E259F59 /* TyphoonStoryboardDefinition.h */; settings = {ATTRIBUTES = (Public, ); }; };
44CD842019B1CEA681F820F37A30B7C4 /* SDImageFramePool.h in Headers */ = {isa = PBXBuildFile; fileRef = 45FA653E23538B63DB444A6ECA2E3190 /* SDImageFramePool.h */; settings = {ATTRIBUTES = (Private, ); }; };
4571A0EA37DC84F39E3830D38A1531AB /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C5460C93594FE49E0DF49CC9F802F1F3 /* UIKit.framework */; };
45A2252FE9A80B5B59B021395E19EE0C /* RACTuple.m in Sources */ = {isa = PBXBuildFile; fileRef = 1CB4CE5D415687F6A3499F360794DA04 /* RACTuple.m */; };
45D4E85C7031F33EA83C956C73136DBF /* OCMockObject.h in Headers */ = {isa = PBXBuildFile; fileRef = A9267DE7A89C784B346A9CA842751576 /* OCMockObject.h */; settings = {ATTRIBUTES = (Public, ); }; };
45F861348548E0CD9D2C0781E04B3CEA /* RACPassthroughSubscriber.h in Headers */ = {isa = PBXBuildFile; fileRef = CB8729BC7993BA548B4D892BE63AEE83 /* RACPassthroughSubscriber.h */; settings = {ATTRIBUTES = (Public, ); }; };
4688743B7B845309486559EB7BD5D147 /* SDWebImageCompat.h in Headers */ = {isa = PBXBuildFile; fileRef = 24A117E496A0E15BE008AF6C24618D1F /* SDWebImageCompat.h */; settings = {ATTRIBUTES = (Public, ); }; };
46FE7652F3F63388BF97D9C8B7015A6B /* RACUnarySequence.h in Headers */ = {isa = PBXBuildFile; fileRef = FB49799876CD2FF91D515D753BBB6D1F /* RACUnarySequence.h */; settings = {ATTRIBUTES = (Public, ); }; };
4775DBD4961BF6BA60BA6ADECCCF03DA /* OCMBoxedReturnValueProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = ED4B3532992A08C1DE0094AFAED64A03 /* OCMBoxedReturnValueProvider.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
47987D5D284981A7B1FDA8EDAD4EA80E /* TyphoonInjectionByFactoryReference.m in Sources */ = {isa = PBXBuildFile; fileRef = DA6A12A58C1C3043FB0D92D39EF14EB7 /* TyphoonInjectionByFactoryReference.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
47BB76E62236FE1305B71F3D4484E52B /* TyphoonBlockDefinitionController.h in Headers */ = {isa = PBXBuildFile; fileRef = 7C9741B2966A4017CFF5F1396954F520 /* TyphoonBlockDefinitionController.h */; settings = {ATTRIBUTES = (Public, ); }; };
47E27473C6C3DAD243E76BBB9FAAC38A /* YYModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 325FC278EFE1EDE9A708FE21611C503E /* YYModel.h */; settings = {ATTRIBUTES = (Public, ); }; };
4851743937896F7DBEC0BCC7A878682C /* NSObject+RACDeallocating.h in Headers */ = {isa = PBXBuildFile; fileRef = F383EDD80DA3184C9A032B89FD763BB7 /* NSObject+RACDeallocating.h */; settings = {ATTRIBUTES = (Public, ); }; };
48916DE9521F627589300512ECC2D4A5 /* NSButton+WebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 0F07E5AA6AC318EED47D4865477C9372 /* NSButton+WebCache.m */; };
4958D2D0D6DC37810FAB1105D10E90D3 /* UITextField+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 17092355C96C3E633D28031E208BC50C /* UITextField+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
49C540A33BC7DB59595582A72D92E8FF /* TyphoonStoryboardResolver.h in Headers */ = {isa = PBXBuildFile; fileRef = BC0D43AD044D143B02D4E1D5EA86BE86 /* TyphoonStoryboardResolver.h */; settings = {ATTRIBUTES = (Public, ); }; };
4B2C2AE16AE3DDA7417AFCF7952588F1 /* SDImageAssetManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 6ED217D4C98D62892AD92759A98D2928 /* SDImageAssetManager.h */; settings = {ATTRIBUTES = (Private, ); }; };
4BCA5F8C6F0C73C76E656BA0E3EA858F /* RACTargetQueueScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B91A0326C0D75CA3ED540C456F99FA7 /* RACTargetQueueScheduler.m */; };
4BE76E020E0B05ADDB89CCA00CFB9933 /* NSObject+PropertyInjection.m in Sources */ = {isa = PBXBuildFile; fileRef = 69139A572FFAF2E425701DCD352635E3 /* NSObject+PropertyInjection.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
4C4C5CBDDABB5F5850DFE69DB34F6AD7 /* TyphoonInjectionByReference.m in Sources */ = {isa = PBXBuildFile; fileRef = DC81E927C43FEC142A408A3850366DA7 /* TyphoonInjectionByReference.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
4D2C79AB2D24CFEC864F08D913CE7692 /* SDImageCodersManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 85BADAC4F3DCD2CDEA7EE692773C47A3 /* SDImageCodersManager.m */; };
4E104BAD7705D8D5D9F45D41535914EE /* OCMRealObjectForwarder.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E440830ABB9D3025B26A80937A2EFE9 /* OCMRealObjectForwarder.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
4E64E3ACB0070325287252B917BA9EF3 /* NSString+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 06DAA5ED399AAB042406902A676EED50 /* NSString+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
4ED05DB3E43FF6AE1FA22130B2B50F05 /* UIImage+MemoryCacheCost.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D3D8BCFF92388848C3FD597F1895318 /* UIImage+MemoryCacheCost.h */; settings = {ATTRIBUTES = (Public, ); }; };
4ED8D4C89D65CD9BAFF98BA1EA8543BE /* RACPassthroughSubscriber.m in Sources */ = {isa = PBXBuildFile; fileRef = 472F840FC38C62F9E93AD1BEE1A70F95 /* RACPassthroughSubscriber.m */; };
4F2506F20F6E778B71CC643C0C76F805 /* RACReplaySubject.m in Sources */ = {isa = PBXBuildFile; fileRef = EA318A2FA8725340866FC3083915FCFD /* RACReplaySubject.m */; };
4F35842E1298A391EC638FE90727CC31 /* TyphoonInjectionByComponentFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 050CAE5EF6AFDE7FE32CEE7E030CDD0E /* TyphoonInjectionByComponentFactory.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
4F599FFE9C09DD9532BD46E758387116 /* TyphoonSwizzlerDefaultImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = 95DC39645A666C2B0AF83523FAD30758 /* TyphoonSwizzlerDefaultImpl.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
4F650AA71665246CC359F05EA7B041D0 /* TyphoonBlockDefinition+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 1498864CCF5B387AE86A3D8C74065926 /* TyphoonBlockDefinition+Internal.h */; settings = {ATTRIBUTES = (Public, ); }; };
4FE0E785F97144E411FCE8F30C51F7EA /* RACImmediateScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = F0C0E50FA94D388C9007312869F096D5 /* RACImmediateScheduler.m */; };
5005432EAECE0BBCAE0487FB541489F7 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D376F0D7BDE2FCC4DDC6743AFC8DDA9E /* Foundation.framework */; };
50559EFD7F12C78B1448D757DCC81218 /* RACGroupedSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = 38B6C5CA78D03B16417EF54377D96D24 /* RACGroupedSignal.m */; };
5111A0A0934551CD2B9DDB1A1CA79FA7 /* SDAnimatedImageRep.m in Sources */ = {isa = PBXBuildFile; fileRef = 0571A2C7BE7E0945F4212CD552F88BE1 /* SDAnimatedImageRep.m */; };
5137D94F9CA1FAE507602788BC4988A2 /* OCProtocolMockObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 7F84B399A305F1F7EAFB02599E56A1B1 /* OCProtocolMockObject.h */; settings = {ATTRIBUTES = (Project, ); }; };
5146309CBB46C9B12CC1648AF02C3A20 /* TyphoonParentReferenceHydratingPostProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 18FB517C4077E6929DB734C793782E40 /* TyphoonParentReferenceHydratingPostProcessor.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
521D285E66EFF69011341B410DE59767 /* TyphoonPropertyStyleConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 720CFEBFB23833C0091EFD8767731EA7 /* TyphoonPropertyStyleConfiguration.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
526485EF6D2B62B24DB59122FB94BD42 /* SDDeviceHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D1BD5E2CF0B45A47FC6BC88583E5B26 /* SDDeviceHelper.m */; };
52B6E27260DC936E3AAE8F2F46CFB416 /* RACEXTKeyPathCoding.h in Headers */ = {isa = PBXBuildFile; fileRef = F4690548BF3F6C5A71CDC7BF7187EC20 /* RACEXTKeyPathCoding.h */; settings = {ATTRIBUTES = (Public, ); }; };
52D27AEA262A912CA0A4F72D1BD32586 /* OCMPassByRefSetter.m in Sources */ = {isa = PBXBuildFile; fileRef = C14BDA045B4B1D88E3900EFD02FFA862 /* OCMPassByRefSetter.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
5308E660E723C11E7691D311FD59C459 /* SDDisplayLink.m in Sources */ = {isa = PBXBuildFile; fileRef = FB0CBD82662E0CBDB55A48F245646888 /* SDDisplayLink.m */; };
53433003112C4FE271EC985803862B61 /* SDWebImageCacheKeyFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = F5AEF10212D323726736313347CAA30B /* SDWebImageCacheKeyFilter.h */; settings = {ATTRIBUTES = (Public, ); }; };
53EF718B00DF8A17F05A50B6E478E799 /* TyphoonInjectionByType.h in Headers */ = {isa = PBXBuildFile; fileRef = 2C379332A0D51473ADCF63C80BF3B4C8 /* TyphoonInjectionByType.h */; settings = {ATTRIBUTES = (Public, ); }; };
540484CA48AE6E538865EE0882E9B886 /* UITextView+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 02580255476480314DC90D373EF87D94 /* UITextView+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
543A777E19623D18C1389BC6189F21FC /* TyphoonAutoInjection.h in Headers */ = {isa = PBXBuildFile; fileRef = D16AFD377C51E109866560DEE9CBC39E /* TyphoonAutoInjection.h */; settings = {ATTRIBUTES = (Public, ); }; };
54C6DD71F9BA9CA676096215DDE74996 /* UIGestureRecognizer+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E295017E5EF894DD2C62B5422434280 /* UIGestureRecognizer+RACSignalSupport.m */; };
550E329852F1C92D6E70854088B4D5D8 /* TyphoonInjectionContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 227C536B9341DFDDAB8A93622D7BAB18 /* TyphoonInjectionContext.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
550F4E490798ADB3B4B2C706CEEA4A8C /* TyphoonConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 30935A228E64CE3387EE71949306F225 /* TyphoonConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; };
5558D029B8BABF2823BAB0A4A689C100 /* TyphoonViewControllerFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 71C1C00FFE25AA899E9B77567B46E8CA /* TyphoonViewControllerFactory.h */; settings = {ATTRIBUTES = (Public, ); }; };
55F7C7F055A18044497F8C88CAE34118 /* SDImageCachesManagerOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 273504B50C9E20A280FF38B369EECE87 /* SDImageCachesManagerOperation.m */; };
561D1F03868EC8BA84A2B6A22031CB49 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D376F0D7BDE2FCC4DDC6743AFC8DDA9E /* Foundation.framework */; };
56977A714C1767541B64A6D00C7BA62D /* UIResponder+TyphoonOutletTransfer.m in Sources */ = {isa = PBXBuildFile; fileRef = BE3BDF1044166B0EEE411717FD217E42 /* UIResponder+TyphoonOutletTransfer.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
56B73AB5484B65F2C078198B0775AB1D /* TyphoonTypeConversionUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EF8BF2E5B7814DF672FB319D2CB899A /* TyphoonTypeConversionUtils.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
56E800EB3B2BE8AE0BA45A30974D7920 /* Masonry-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 7BDD9A3C6FE6300A4953E8AC4C585381 /* Masonry-dummy.m */; };
56FE435145D35628CB94B8DBD67E81F4 /* RACStream.h in Headers */ = {isa = PBXBuildFile; fileRef = 1DD1EDA8854FDDBB1314904C848EEADC /* RACStream.h */; settings = {ATTRIBUTES = (Public, ); }; };
571C90CBC5777D2194962B65F994FE72 /* TyphoonPrimitiveTypeConverter.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B69333B75E26F15AA2E42D06EF62B15 /* TyphoonPrimitiveTypeConverter.h */; settings = {ATTRIBUTES = (Public, ); }; };
5722A8075D663DC1E070027A450C21AA /* TyphoonCollaboratingAssemblyProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 6027F1EC9BC5F3AB67DFFEC9E6DD6BEB /* TyphoonCollaboratingAssemblyProxy.h */; settings = {ATTRIBUTES = (Public, ); }; };
5806683E752BD6A1B02EE96BE0741385 /* TyphoonAssemblySelectorAdviser.h in Headers */ = {isa = PBXBuildFile; fileRef = 772D4121E01F86089D23DBEBD83FC5AD /* TyphoonAssemblySelectorAdviser.h */; settings = {ATTRIBUTES = (Public, ); }; };
585920A1A37B315FCA853A232773CC12 /* RACStringSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B2FCD3B157174CAD7F173B5F1FF0DA1 /* RACStringSequence.m */; };
5884BE5A453A9E0417728E3FB8063EDD /* GCDAsyncSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 2735EF71996F30815132D35A2A30E845 /* GCDAsyncSocket.h */; settings = {ATTRIBUTES = (Public, ); }; };
5889FA7EB7333745C27C7FACAA9FBB87 /* UIControl+RACSignalSupportPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 8D76E7370C1E6F4EE5AE4D70E5C4D2C2 /* UIControl+RACSignalSupportPrivate.h */; settings = {ATTRIBUTES = (Private, ); }; };
58E512BD916CDD9631428F30A623D370 /* RACTargetQueueScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = BDC02EE21DEF11D396F51D141C543D5C /* RACTargetQueueScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; };
58F7CE37BB4CB3BE806B68A502E6E1A7 /* SDWeakProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 6176156DAB1442AF8B5FF1B18364793C /* SDWeakProxy.h */; settings = {ATTRIBUTES = (Private, ); }; };
596180E0EC9F46D12BA840DC4AA62659 /* UIImage+MemoryCacheCost.m in Sources */ = {isa = PBXBuildFile; fileRef = 46B265A7E81C9ECE01048E340D45E9A3 /* UIImage+MemoryCacheCost.m */; };
596AEAE7A03137CB24B6D05E3B4E47B7 /* RACIndexSetSequence.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D9038CEC928B9AF5CFA2FD0B75B9FEF /* RACIndexSetSequence.h */; settings = {ATTRIBUTES = (Public, ); }; };
597E390C0BBB75B8045B651C487C2034 /* SDImageAWebPCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 15C365C1912A0FB488CECEF9950319ED /* SDImageAWebPCoder.m */; };
59DD3A20CB015329E4F181DE7B1A7373 /* TyphoonSwizzlerDefaultImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = CE6F1B69B9CDD8F4C2903BEDC2660DC3 /* TyphoonSwizzlerDefaultImpl.h */; settings = {ATTRIBUTES = (Public, ); }; };
5A592D4D0B89373E214B385BF7D4076D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D376F0D7BDE2FCC4DDC6743AFC8DDA9E /* Foundation.framework */; };
5A6AFAF7515C84B07422A61E94280EB6 /* RACSignalSequence.h in Headers */ = {isa = PBXBuildFile; fileRef = 7D81778D9E6487677837D58B13894E94 /* RACSignalSequence.h */; settings = {ATTRIBUTES = (Public, ); }; };
5B08596E856E4CC2F34A8A2372F9F764 /* NSArray+MASAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = A4C87452D870EC6849B6A78C6E8FC492 /* NSArray+MASAdditions.m */; };
5B95C0042EA30E36A37E4F686F05DE96 /* OCMVerifier.m in Sources */ = {isa = PBXBuildFile; fileRef = 83104DD312D9864E2BAA39D25537ADAA /* OCMVerifier.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
5C7199812C951AD5E976A6AA29A30BD5 /* RACReplaySubject.h in Headers */ = {isa = PBXBuildFile; fileRef = 141EB3C3A6A1510AA72DD2C9ACD6894C /* RACReplaySubject.h */; settings = {ATTRIBUTES = (Public, ); }; };
5C8279C226EB028B044C5A0F4AC5A91A /* SDAssociatedObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 19EA01E604C674E56BA259D0C3794651 /* SDAssociatedObject.h */; settings = {ATTRIBUTES = (Private, ); }; };
5CFCD2D66F81B7F1AE4D8E82A51AFDCF /* NSMethodSignature+OCMAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = AACE72B147E103C5ABF8DD49C677152C /* NSMethodSignature+OCMAdditions.h */; settings = {ATTRIBUTES = (Project, ); }; };
5D3BF2C909D103968F9FCBC99918728D /* TyphoonTypeDescriptor.m in Sources */ = {isa = PBXBuildFile; fileRef = C7F186EA53F19BDDCBCD2E2E2279D348 /* TyphoonTypeDescriptor.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0 -DOS_OBJECT_USE_OBJC=0"; }; };
5DCBA14510E091D6A1CE499B08B794B5 /* UIImage+Metadata.h in Headers */ = {isa = PBXBuildFile; fileRef = DAF7C7F3C941B652A3015DFEA15DB259 /* UIImage+Metadata.h */; settings = {ATTRIBUTES = (Public, ); }; };
5E10328A83E05D0015D7459FAAEF121D /* SDGraphicsImageRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = C49042227AF27E657FDC7BA3CA8A537A /* SDGraphicsImageRenderer.h */; settings = {ATTRIBUTES = (Public, ); }; };
5F45735DF355530CC955066D3C007E19 /* MASViewConstraint.h in Headers */ = {isa = PBXBuildFile; fileRef = D35CCCA0DFE0CE1EBDA590032372C0AB /* MASViewConstraint.h */; settings = {ATTRIBUTES = (Public, ); }; };
5F5426608460A8B17C1AE5C2351BAA19 /* UIScrollView+EmptyDataSet.m in Sources */ = {isa = PBXBuildFile; fileRef = 91DE4AD4922A1500C3D8808438EBC84A /* UIScrollView+EmptyDataSet.m */; };
5F60001126438149B722B4CB58B32CC5 /* TyphoonInjectedObject.m in Sources */ = {isa = PBXBuildFile; fileRef = F42860013A956ABF313EE9C9D084F191 /* TyphoonInjectedObject.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
5F8F021870002E9A2776909ACB965E3E /* NSString+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D163BF4979C587AB2708ADA67ABB425 /* NSString+RACSequenceAdditions.m */; };
61507E402F1F7C58BF119995A0479A22 /* NSArray+MASShorthandAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 041C0D0F13831619F5479D99056A302A /* NSArray+MASShorthandAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
616A8338C42FB01748DF1BDDA944858D /* UIView+WebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 50D1100A5FC66E513CDCEFED2FEC8138 /* UIView+WebCache.m */; };
61B4CC930FD22979260025929C94D3F5 /* OCMock-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = A2ABDBBFB28FCCED57D0D680E16EBA76 /* OCMock-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
6215CAAC4B72DECED49E706D88C3B66D /* RACDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 67EB4C9AA76FDBC2CD3FFBA7EE37A27C /* RACDelegateProxy.m */; };
627357C7FD3404F2ABFA952B05FECE95 /* TyphoonAbstractInjection.m in Sources */ = {isa = PBXBuildFile; fileRef = 9EB5CC7B3930FE88F26ECB6C25761E21 /* TyphoonAbstractInjection.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
62C99100FB8B0AB574F6CA3B6EE47C61 /* TyphoonInjectionByFactoryReference.h in Headers */ = {isa = PBXBuildFile; fileRef = 891C96D521BE0268A6C2A44DF6CF804B /* TyphoonInjectionByFactoryReference.h */; settings = {ATTRIBUTES = (Public, ); }; };
62FE895DF9D65A2955A275D909ECBE18 /* SDAnimatedImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 64094E39E3BA0988064CD05A86418E97 /* SDAnimatedImageView.m */; };
633D48FC60D6C9C2BEEE6F9D7FB9FB31 /* NSInvocation+TCFCustomImplementation.h in Headers */ = {isa = PBXBuildFile; fileRef = 1BDDCD9A639B9E7EBCAF02C54E1DE1E3 /* NSInvocation+TCFCustomImplementation.h */; settings = {ATTRIBUTES = (Public, ); }; };
63C0FC58D691FE32021C980B486A4FE7 /* TyphoonPlistStyleConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 87B55C9AC3241F2442119F64473D1F41 /* TyphoonPlistStyleConfiguration.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
6553A191C05881C622793B5DD6D312D0 /* TyphoonPathResource.m in Sources */ = {isa = PBXBuildFile; fileRef = CDA3D0B455A13ACC94AFADCBB2E311FE /* TyphoonPathResource.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
659C4CDCC04F165FA3A3C4FDF3DA6A2C /* UIViewController+TyphoonStoryboardIntegration.h in Headers */ = {isa = PBXBuildFile; fileRef = 5AD0D1CF37F873A7C98560DA90D7922B /* UIViewController+TyphoonStoryboardIntegration.h */; settings = {ATTRIBUTES = (Public, ); }; };
66187A77817A27C7EAA46CA9E1183C67 /* RACEmptySequence.m in Sources */ = {isa = PBXBuildFile; fileRef = DB2ED745168A43281FE03EC58108FBDE /* RACEmptySequence.m */; };
67178A8153B1A2F1D0D544B8093E23C5 /* SDAnimatedImageView+WebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 826AE09BE9A75B5E4F07DCA8BD6E568A /* SDAnimatedImageView+WebCache.m */; };
6720FB4D7E520BC198713915B63D0369 /* TyphoonAssemblyBuilder+PlistProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = C5F745BEA95E2A3A7A18764A8CE22A3F /* TyphoonAssemblyBuilder+PlistProcessor.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
67607C80710BD7E0B43D74104EE602AE /* OCMQuantifier.h in Headers */ = {isa = PBXBuildFile; fileRef = 5603722E8B41BC93F91BC672658EA10E /* OCMQuantifier.h */; settings = {ATTRIBUTES = (Public, ); }; };
676775CB29378BB6CA3CA5992E9C6A99 /* SDImageIOAnimatedCoderInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = C15E41DEC1751BDADA070E77A5D31D94 /* SDImageIOAnimatedCoderInternal.h */; settings = {ATTRIBUTES = (Private, ); }; };
67A2E8F472D342E94FAA643110E9E184 /* TyphoonBlockDefinitionController.m in Sources */ = {isa = PBXBuildFile; fileRef = E842E755FB8A20406553415C9EACC8A1 /* TyphoonBlockDefinitionController.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
681A93DD87FCA7FCAFF578C095B6B9E7 /* TyphoonAssemblyDefinitionBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = 8D3BAA9F0A9900B2F727C2916F30C12A /* TyphoonAssemblyDefinitionBuilder.h */; settings = {ATTRIBUTES = (Public, ); }; };
694B8697854A776E32032999B2EF1FEA /* UIImage+Metadata.m in Sources */ = {isa = PBXBuildFile; fileRef = FF9570A386DE6A77332680BFBB13573F /* UIImage+Metadata.m */; };
695E3BEA0C9766D4974998CF17C090BD /* NSDictionary+CustomInjection.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FB2623C122163DC9FB824E1A71D49FD /* NSDictionary+CustomInjection.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
69A06A02F52EB26259FAD1DF6B121BE1 /* SDCallbackQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 746C9C6D96CC90B1558DBCDA1EF575B4 /* SDCallbackQueue.m */; };
69AB6A513D5F36D7360FEF4FDA1D60D0 /* UIView+WebCacheState.h in Headers */ = {isa = PBXBuildFile; fileRef = 4C20ADFFD788112D9472742A2E3ACB43 /* UIView+WebCacheState.h */; settings = {ATTRIBUTES = (Public, ); }; };
69B92D1883A2D2069444FAE5AAE6272A /* CocoaAsyncSocket-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 6D7D2C3B5323BC3D3465786EE817BE2D /* CocoaAsyncSocket-dummy.m */; };
6A19379E3B0370EDA447743C9B1A1379 /* UIImageView+HighlightedWebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 907EE3AB436219C47FE62434C1DCFB33 /* UIImageView+HighlightedWebCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
6A4C0518640F1FF5CB8024E732E9DF95 /* MKAnnotationView+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D39AF61764732228A5146A67595AEAE /* MKAnnotationView+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
6AA16C78FAF9F27ADBA06E4AC494A490 /* NSArray+TyphoonManualEnumeration.h in Headers */ = {isa = PBXBuildFile; fileRef = D524BC409D8FA54C1A06A2E06C1FC36A /* NSArray+TyphoonManualEnumeration.h */; settings = {ATTRIBUTES = (Public, ); }; };
6B0978C9398336656EE309E62060AEAB /* SDImageAssetManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 8233301E93D80EEC51CC7AFF84085AC7 /* SDImageAssetManager.m */; };
6B5C3592B5E911E833D067D0BC785B1A /* SDImageFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 3D4CB8BAE1E9433AA4D034F31AD60DE4 /* SDImageFrame.h */; settings = {ATTRIBUTES = (Public, ); }; };
6BF2FA3FD858A08305204D1A39AC6AB5 /* TyphoonDefinitionRegisterer.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AC6505E33748BB6A720F5B5C8C96382 /* TyphoonDefinitionRegisterer.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
6C2605B3C710A00D92D6D5059642EDDA /* TyphoonColorConversionUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 065E52ED2D30B3B331A00D8EB564E4D8 /* TyphoonColorConversionUtils.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
6CDB988A83D6057DEBDEE651EAB17FB9 /* TyphoonInjectionByCollection.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DA26B2B02963DB127DA3542FECF3472 /* TyphoonInjectionByCollection.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
6CEAFC9DBB829A1A09338FCC0E9072D2 /* OCMInvocationMatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 14BA0C92570BFCBF28C82F32F37A96B6 /* OCMInvocationMatcher.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
6D03C8262D633E11DFF26765A28B3916 /* NSValue+TCFUnwrapValues.h in Headers */ = {isa = PBXBuildFile; fileRef = 10AF2F5387AC99FDF1EE38CC6AF81A5A /* NSValue+TCFUnwrapValues.h */; settings = {ATTRIBUTES = (Public, ); }; };
6D316F209F073EEEC9898B2DED020AF8 /* RACChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = ADA38AFC2E679FBA25375054E69DEFB2 /* RACChannel.m */; };
6D423F8538D6266DB24DAFFED462D46C /* TyphoonComponentFactory+InstanceBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = FAEAEC0032244300776CF7AFB0401C03 /* TyphoonComponentFactory+InstanceBuilder.h */; settings = {ATTRIBUTES = (Public, ); }; };
6D7651C8B1EA18B2EB5278952B19FC15 /* TyphoonMethod+InstanceBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E9BA0379B500368359C8D6E3C90BE50 /* TyphoonMethod+InstanceBuilder.h */; settings = {ATTRIBUTES = (Public, ); }; };
6D9949D8FE86A2D522CB6C880B8E63CD /* NSString+RACKeyPathUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EA9D6ACAA4596DDBF52BD0B1662EB44 /* NSString+RACKeyPathUtilities.m */; };
6E261D72B6817096F7A6E136C3B32A9F /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = A420909D1D09A79AE6103170D58A4332 /* Reachability.m */; };
6E66305665DBCFBCF5B2480BF705D500 /* SDWebImageTransition.h in Headers */ = {isa = PBXBuildFile; fileRef = A16A247B48BF9C8E1B54650152D9F47E /* SDWebImageTransition.h */; settings = {ATTRIBUTES = (Public, ); }; };
6E95C1B16744B10481BFAAF67AA8D5F7 /* TyphoonStoryboardProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = A96DEC36622CF8577C0E003138EFD7EE /* TyphoonStoryboardProvider.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
6EFEEE3AE22E97DCEC4F5A3B88F56FC7 /* SDImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 3E8A97304D179B0ADAF704C7CEBEECD1 /* SDImageLoader.m */; };
6F3637EE643EABB1DE9212EA68649A64 /* UIColor+SDHexString.m in Sources */ = {isa = PBXBuildFile; fileRef = A67FE7A5F980382C477CC0E5671FB1B3 /* UIColor+SDHexString.m */; };
7008DDC9510D90489B283BEF7993789C /* TyphoonPropertyStyleConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = CA332156FBF114376E2FE5077E71C03E /* TyphoonPropertyStyleConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; };
700FCD9C14FA276BBED0A2A34D274808 /* OCMPassByRefSetter.h in Headers */ = {isa = PBXBuildFile; fileRef = 5881CE458D78CE4C7C6B914138FDE55B /* OCMPassByRefSetter.h */; settings = {ATTRIBUTES = (Project, ); }; };
701B922F54EA9A9A9FDA2C17CE75CDE0 /* TyphoonReferenceDefinition.h in Headers */ = {isa = PBXBuildFile; fileRef = FD2C416CACE6B5586F404FC1E9F617FA /* TyphoonReferenceDefinition.h */; settings = {ATTRIBUTES = (Public, ); }; };
7074EA7FCC90B4967A437F5C43496828 /* SDDisplayLink.h in Headers */ = {isa = PBXBuildFile; fileRef = C40D9702A810253531E2904C61CE32F5 /* SDDisplayLink.h */; settings = {ATTRIBUTES = (Private, ); }; };
711D32EF4A9901567A488291603BF906 /* SDWebImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 81E027BECECC686145B8874E339146B3 /* SDWebImage.h */; settings = {ATTRIBUTES = (Public, ); }; };
716F02FA5B659533261BD4BEE3695C1C /* TyphoonPassThroughTypeConverter.m in Sources */ = {isa = PBXBuildFile; fileRef = 75013D4007A6A538ACAE4686AAC93DE4 /* TyphoonPassThroughTypeConverter.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
717F76926C7BCB5B10C3037AD9239084 /* SDImageIOCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 74A9B68BB1F2C7DC615156DBA4077781 /* SDImageIOCoder.m */; };
71A9A14223B711ED398479921B0D099B /* RACEmptySequence.h in Headers */ = {isa = PBXBuildFile; fileRef = A9638C9372C5407A0209909EB7BAFDCA /* RACEmptySequence.h */; settings = {ATTRIBUTES = (Private, ); }; };
71BEB1D9532900291A5A24B1C038516F /* UIColor+SDHexString.h in Headers */ = {isa = PBXBuildFile; fileRef = 56D32E1C48670C43C54173C7D2B28771 /* UIColor+SDHexString.h */; settings = {ATTRIBUTES = (Private, ); }; };
71E3627F3118542E4CF67F2F67621BD7 /* TyphoonMethod+InstanceBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 34517F02303D979079D891F86B87BD5D /* TyphoonMethod+InstanceBuilder.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
71F2B8CBB99087F348C472230200586F /* SDGraphicsImageRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C9B76DC6B005BE0C06B737507138A3E /* SDGraphicsImageRenderer.m */; };
72493549A72116EBF5CAF735CD0AEA80 /* RACSequence.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A2036089AC8297FFEE003F786496211 /* RACSequence.h */; settings = {ATTRIBUTES = (Public, ); }; };
72AE6D5A79C2CC6D5E6578F9644C654B /* UIAlertView+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 7EB4C9DFCDF6EB86413C60976F25BE67 /* UIAlertView+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
73484D39A92E83DF9E2B56D524ECA9C5 /* NSObject+RACDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = 72274AF631E665EB74378E781E9A7437 /* NSObject+RACDescription.h */; settings = {ATTRIBUTES = (Public, ); }; };
73574D4AAB1219ED412B3D2A6D1E321F /* UIStepper+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = AD8E6EFAEA3F539F1D1F0B9752F7E7EF /* UIStepper+RACSignalSupport.m */; };
73A70EE2C9C9FC76C6495945F3F0DC4D /* RACDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = 419B699C2112F512494E7DDB5FE14FCA /* RACDisposable.m */; };
7434021F1AFA292EBE7A85C66AFD33AC /* TyphoonTestUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = E586B047153D54E5818AFAA7B25CC55A /* TyphoonTestUtils.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
74C474676C69A80BEC29B0F55FDF4D19 /* UIView+WebCacheState.m in Sources */ = {isa = PBXBuildFile; fileRef = 5517D5F4CC5D9F18F26A5ED4C9034C6D /* UIView+WebCacheState.m */; };
74E069F8C9E22C0E37F261A5AB03A613 /* SDWebImageDownloaderConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = CDACF51466350F7A4B6A82E206A3F37A /* SDWebImageDownloaderConfig.m */; };
752822FE3F5092322D18FEC4533B79A9 /* SDWebImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B0A54B1F889B9B87CDFE1DB41F6ABCC /* SDWebImageDownloader.m */; };
756F7CB95E7C108324A2F7B8FBF6B17A /* TyphoonAssembly.h in Headers */ = {isa = PBXBuildFile; fileRef = E54E4A10B9B5F3F352208D4FC87A2ECF /* TyphoonAssembly.h */; settings = {ATTRIBUTES = (Public, ); }; };
75771A97B77FA30A0175A81B480F80EF /* UIImage+ForceDecode.h in Headers */ = {isa = PBXBuildFile; fileRef = FA43EAB3E19854BD7C0DF6BA3C16B9AF /* UIImage+ForceDecode.h */; settings = {ATTRIBUTES = (Public, ); }; };
76FE013CAA5D3972F17B96497E3DBC56 /* RACDynamicSequence.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E69DD9AA22CCF06F1224BEB3E20EDD3 /* RACDynamicSequence.h */; settings = {ATTRIBUTES = (Public, ); }; };
772CF8E9CD02ECA4275B6173E2110E80 /* View+MASShorthandAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 68E47146ED471AD73B71268A94D9F69D /* View+MASShorthandAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
777DF7EE911DE5E8A67933FF88906269 /* OCMNotificationPoster.m in Sources */ = {isa = PBXBuildFile; fileRef = FCD14B4785B937EC852B628A8B7F5227 /* OCMNotificationPoster.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
78DB67A6B39E0407C2987B9841C0B052 /* TyphoonFactoryPropertyInjectionPostProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = 458457A3A3CABCBDF2136A3F5E6E82BF /* TyphoonFactoryPropertyInjectionPostProcessor.h */; settings = {ATTRIBUTES = (Public, ); }; };
78EF98028F832D934652FDFF7527F595 /* TyphoonPlistStyleConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E5A663C1E60087F575DDB601D6017F3 /* TyphoonPlistStyleConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; };
78FF43FC286BBB9D9B0D6AFADC6F0916 /* TyphoonDefinition+Option.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B345A1E24575BA9D0BC33710FE943C0 /* TyphoonDefinition+Option.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
79CCECA8A1FF25E880AB21DA8BF337A6 /* TyphoonAssemblyBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = 43C103B5C9A8BC55D3644883BC36B3A6 /* TyphoonAssemblyBuilder.h */; settings = {ATTRIBUTES = (Public, ); }; };
79D360313699E9086FEDED32E78C12B4 /* TyphoonOptionMatcher.h in Headers */ = {isa = PBXBuildFile; fileRef = CF5D6355EBCEC215D5F4264D1241E1B1 /* TyphoonOptionMatcher.h */; settings = {ATTRIBUTES = (Public, ); }; };
7A2A5EFE8571CB8A7E354E536997AD1B /* OCProtocolMockObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 78E1C3D8226EEA6918CB1B8A96FC843A /* OCProtocolMockObject.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
7A4EB9ED5D4E03170FFE61FCB299687B /* SDAnimatedImagePlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = F77D3BA16F4A5B6BBE117AA9B4BC653E /* SDAnimatedImagePlayer.m */; };
7A95ACE7157BEA7C34CE60ECCCD73F1C /* NSLayoutConstraint+TyphoonOutletTransfer.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DF90E5FD761A704C337FA27D910B4EC /* NSLayoutConstraint+TyphoonOutletTransfer.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
7AD50450C1AC9BC993C2911D83D14146 /* TyphoonStackElement.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B68D1A525FF5B8E36976449BC5C2434 /* TyphoonStackElement.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
7C03AB94ABD7FA82C939386C8827E0B4 /* TyphoonNSNumberTypeConverter.h in Headers */ = {isa = PBXBuildFile; fileRef = 13D762E3256505E91BAEA28CEAA5F68F /* TyphoonNSNumberTypeConverter.h */; settings = {ATTRIBUTES = (Public, ); }; };
7C1725EF13F888580C9E2F3057B33FA4 /* Typhoon+Infrastructure.h in Headers */ = {isa = PBXBuildFile; fileRef = 76C8CC4DEAB56D01254A5E21663A9CB3 /* Typhoon+Infrastructure.h */; settings = {ATTRIBUTES = (Public, ); }; };
7C28695EB07687D1CC58A6B130EEBA54 /* TyphoonReferenceDefinition.m in Sources */ = {isa = PBXBuildFile; fileRef = C2D79FFE2E4790A5C03593BBCA678B0A /* TyphoonReferenceDefinition.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
7C45DBA62EE045C4922404182F6393B8 /* SDWebImageError.h in Headers */ = {isa = PBXBuildFile; fileRef = D6E9B831B9FB4D942099CD004CE0BAC3 /* SDWebImageError.h */; settings = {ATTRIBUTES = (Public, ); }; };
7C5505A2D3F2A697A5F324787061F4B7 /* MASConstraint+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = E5AE3EB7707FD5429801C6CB2A8A382E /* MASConstraint+Private.h */; settings = {ATTRIBUTES = (Public, ); }; };
7C6A24B103E14B07F00100A020D9BA7D /* YYModel-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B744979B63E3F8D6CD8B04CCE7E0E58 /* YYModel-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
7C9155F50E0008C9C19C443D4257818A /* OCMock-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = B1233E43A91D5DA56B2AA3A634E7BF05 /* OCMock-dummy.m */; };
7C92A7B83580ADE50BF1E77BE494445A /* TyphoonStoryboard.h in Headers */ = {isa = PBXBuildFile; fileRef = 384D578EC3A290F1ADF6C1728A6C1562 /* TyphoonStoryboard.h */; settings = {ATTRIBUTES = (Public, ); }; };
7CA28A9549AA6EC233E0F26268EE312E /* TyphoonObjectWithCustomInjection.h in Headers */ = {isa = PBXBuildFile; fileRef = 661A8C92540E00912555E0561EB50777 /* TyphoonObjectWithCustomInjection.h */; settings = {ATTRIBUTES = (Public, ); }; };
7CBDB5FECCE29FAD4BB0D8E763FFDE83 /* TyphoonUIColorTypeConverter.m in Sources */ = {isa = PBXBuildFile; fileRef = D421E7A95F7BBA522BE9AE982E7390CF /* TyphoonUIColorTypeConverter.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
7D2E8066457576BA5C42D43C38FDE09B /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08703C437757C9668B19065994EA7EBD /* XCTest.framework */; };
7D52D2C3BDE0C22A5305A2619A99E60C /* TyphoonLoadedView.h in Headers */ = {isa = PBXBuildFile; fileRef = 3D455866AC1ECBAFA4D139C7DD0E3168 /* TyphoonLoadedView.h */; settings = {ATTRIBUTES = (Public, ); }; };
7E87D589F135093C179E571A12242622 /* RACSubscriptingAssignmentTrampoline.m in Sources */ = {isa = PBXBuildFile; fileRef = C218F557360FC510BBA92A7B3EEC1065 /* RACSubscriptingAssignmentTrampoline.m */; };
7F455A25066EB5B687D341842A8825F8 /* TyphoonParentReferenceHydratingPostProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = C9703620E8931C6F0907DD19F302A7A9 /* TyphoonParentReferenceHydratingPostProcessor.h */; settings = {ATTRIBUTES = (Public, ); }; };
8042F6C17A7801DC10A84A74341EA593 /* TyphoonNSNumberTypeConverter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FEDDBAED301C3BCF65687E2F5B90003 /* TyphoonNSNumberTypeConverter.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
8085C3E407AA5EF1D9D7376F893F2B6A /* RACMulticastConnection+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = AFCF2DB5C3722AD61AAD5876C0E5E4CF /* RACMulticastConnection+Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
809CF3FAEE0A4B5157D245F04BFEF46A /* TyphoonAssemblyAdviser.m in Sources */ = {isa = PBXBuildFile; fileRef = B8D365F9E6ABE29BE745099B2A29F0B1 /* TyphoonAssemblyAdviser.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
80E157CBA7AE6BD79E34E063F46ED57C /* UIActionSheet+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 276FD7F39BAD92CD4DE60118D6BDC68B /* UIActionSheet+RACSignalSupport.m */; };
80F35FE6EC89AAC77F968AC86618E92A /* UITableViewCell+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A1718181F5BEF030AA0CA446A6686F2 /* UITableViewCell+RACSignalSupport.m */; };
80F8652EFE9F52A3E61806E5E01AE251 /* TyphoonNibLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = F180A82892F507476C7119020F8B59B0 /* TyphoonNibLoader.h */; settings = {ATTRIBUTES = (Public, ); }; };
812856A2F30E87464C6509362FA23ECA /* TyphoonAssemblyAccessor.m in Sources */ = {isa = PBXBuildFile; fileRef = EFB2E394DB7A9A7EEE8003F7C8E6AD40 /* TyphoonAssemblyAccessor.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
813BE4C96A6D39C13EC50C6CD164F0AF /* MASConstraintMaker.h in Headers */ = {isa = PBXBuildFile; fileRef = E3F2828AF28F25FC9663EDF2BD110090 /* MASConstraintMaker.h */; settings = {ATTRIBUTES = (Public, ); }; };
8174C42751AFCD44787AF27A54C9CAB8 /* OCMFunctionsPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 73855182D583882A717B2006B73A0953 /* OCMFunctionsPrivate.h */; settings = {ATTRIBUTES = (Project, ); }; };
81FEC159F4F96E6017E7B841CB99A5F0 /* TyphoonOrdered.h in Headers */ = {isa = PBXBuildFile; fileRef = 08856CD53ABCA9A62737A9BB60FD3496 /* TyphoonOrdered.h */; settings = {ATTRIBUTES = (Public, ); }; };
822F14A51D26702C2C3CDE7FC6FD4559 /* NSObject+RACPropertySubscribing.m in Sources */ = {isa = PBXBuildFile; fileRef = BFB3821735D0527AC675FE95C25658F8 /* NSObject+RACPropertySubscribing.m */; };
8320C45699CAC5719F2A8520B6592F66 /* ReactiveObjC.h in Headers */ = {isa = PBXBuildFile; fileRef = 65BC03570C4B6FD508A02A616BE7DB93 /* ReactiveObjC.h */; settings = {ATTRIBUTES = (Public, ); }; };
83530BF68848CD2C4A79A1FD69B304A5 /* SDImageGIFCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = D69D5EFB2189DEA994DD47111C55B929 /* SDImageGIFCoder.m */; };
8383EB3CD47EA7CC0BB80D288FDA7483 /* NSInvocation+TCFWrapValues.h in Headers */ = {isa = PBXBuildFile; fileRef = F662CD4F60FC2C37B2BEB530D71C3725 /* NSInvocation+TCFWrapValues.h */; settings = {ATTRIBUTES = (Public, ); }; };
83DD7E3A598132752EDA97173C87D115 /* TyphoonAssemblySelectorAdviser.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E2C6F46F631D89037B7C021DF8B3373 /* TyphoonAssemblySelectorAdviser.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
8414CFEEB64ACA817EB88D2FEADDA3B3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D376F0D7BDE2FCC4DDC6743AFC8DDA9E /* Foundation.framework */; };
854807558DCB972EDDFC1D00032BA6E4 /* SDWebImageDownloaderConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B6EEC7E6EC11E55D1422113BAB387BF /* SDWebImageDownloaderConfig.h */; settings = {ATTRIBUTES = (Public, ); }; };
85C08D3AEAC2C12FB06579C326550982 /* RACTestScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = 115C7117F68E39901C8DF436A5193C71 /* RACTestScheduler.m */; };
85C0B4EE334B9972299E62DE61A4BB56 /* SDImageLoadersManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 72AB4D998C2E02839ACA5C88AC0929A9 /* SDImageLoadersManager.m */; };
8632C5137523138C2CE74247D24B9B31 /* Reachability-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B40A0C264DE39B5EEE32E9062A0CCF2 /* Reachability-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
864972FB0DF4B464B1B505AA5F788E91 /* SDInternalMacros.m in Sources */ = {isa = PBXBuildFile; fileRef = 952891A7D50B078702F1A3896E8BA349 /* SDInternalMacros.m */; };
8674138091C088977F9F531E5A61384F /* TyphoonComponentFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 8725F3C4CF3EA8CA846B7A932532168D /* TyphoonComponentFactory.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
8706F7110B1A424205AF1E4A50CDEA7B /* RACScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D249879A5BB3605D92C3BCE919251C0 /* RACScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; };
871E0CD2D0694414A2C0C801E88C93BF /* TyphoonInjectionDefinition.m in Sources */ = {isa = PBXBuildFile; fileRef = 900C7737DD6EC812009057A2F330E295 /* TyphoonInjectionDefinition.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
87564E72B9489B5338ECCEDC18CC9705 /* NSLayoutConstraint+TyphoonOutletTransfer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8EA5D8C4206359854EDBE37A94AF6B6A /* NSLayoutConstraint+TyphoonOutletTransfer.h */; settings = {ATTRIBUTES = (Public, ); }; };
88473AE7C22F952DACB39FA0758D1624 /* SDMemoryCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D6B350707C3EC3D1DAE907D140B8969 /* SDMemoryCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
884B156015530EE607F57D665B076E89 /* TyphoonMethod.h in Headers */ = {isa = PBXBuildFile; fileRef = 08B6FA0A27CA67CF82F94F768E83FBBD /* TyphoonMethod.h */; settings = {ATTRIBUTES = (Public, ); }; };
888E7F1F8876AF0CF9EDA4A866E376AD /* UISegmentedControl+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = B7C029402AB7307D26D094182B34A802 /* UISegmentedControl+RACSignalSupport.m */; };
88A23DF6F5638AC66C28C4102824E8B5 /* NSImage+Compatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = F21C58BD3E6383E8D7CDE5A22DFE4432 /* NSImage+Compatibility.m */; };
89CAFC43896C8BF254CDEAEF1298D287 /* RACDynamicSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = 27871BA973C3B687A1D538285665EBB6 /* RACDynamicSignal.m */; };
89D6769F9AA17FE6F4742FB49196CE8B /* UIDatePicker+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 162947A4139E0A2C2197D6A479D07445 /* UIDatePicker+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
8A0E94F87B74EB4E7C326ABB6FD7BF2C /* ReactiveObjC-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E270F13C426BEA6928171EC9DBCEE70 /* ReactiveObjC-dummy.m */; };
8A5D97580D71011B24EB2F8394C021A3 /* OCPartialMockObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AE4A447234AC4D5BD55348C3E5BC6E8 /* OCPartialMockObject.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
8A66D5CED5EACD00CFB93A5A609B8EA2 /* TyphoonPreattachedComponentsRegisterer.h in Headers */ = {isa = PBXBuildFile; fileRef = 2BE98D49469466E2F555F3E1E65A4F4C /* TyphoonPreattachedComponentsRegisterer.h */; settings = {ATTRIBUTES = (Public, ); }; };
8AF38EDB1E9BF0D334AEB23C488870B8 /* NSData+ImageContentType.h in Headers */ = {isa = PBXBuildFile; fileRef = 610276A9F0EF8CDEC1E67F7E18C1A180 /* NSData+ImageContentType.h */; settings = {ATTRIBUTES = (Public, ); }; };
8B8E0858C48167346060A72979D08AC1 /* RACQueueScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = A86A73484750ADD56A5905600D66D803 /* RACQueueScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; };
8BC4CE9F36D19A09404A29C66145AF28 /* NSObject+RACLifting.m in Sources */ = {isa = PBXBuildFile; fileRef = 032C0CB079DA640D62F451CF2BB09DBF /* NSObject+RACLifting.m */; };
8C6C7E25C5A24C936F81823978190E96 /* ViewController+MASAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 0ED44D2F4F4F552521A65726C787244A /* ViewController+MASAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
8D07F8CA69CF8A20CBC86823852AE944 /* RACUnit.m in Sources */ = {isa = PBXBuildFile; fileRef = B8FF03BB1DD7B75DF4D031C0DA692637 /* RACUnit.m */; };
8D082ED05C36EDF278A1065FBE114F38 /* RACEXTRuntimeExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = AA87031490FD30914ED01BF6A257DB56 /* RACEXTRuntimeExtensions.m */; };
8D8AD606ECD8E1F247965CD43956D412 /* UIImage+Transform.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A8368182067949B6C49AB727784993C /* UIImage+Transform.h */; settings = {ATTRIBUTES = (Public, ); }; };
8DBD580686F262FA6D74D45CEA708336 /* NSNullTypeConverter.h in Headers */ = {isa = PBXBuildFile; fileRef = D655158D8BA8A371D68B4A11241E9616 /* NSNullTypeConverter.h */; settings = {ATTRIBUTES = (Public, ); }; };
8DE844D9A25F4BA974AEB6997F1848FE /* TyphoonAbstractDetachableComponentFactoryPostProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = FE4F16AD593A4CCB580F7CD5B86431EF /* TyphoonAbstractDetachableComponentFactoryPostProcessor.h */; settings = {ATTRIBUTES = (Public, ); }; };
8E0AEE69E2DFB3EC3358444351D332BD /* OCMBlockCaller.m in Sources */ = {isa = PBXBuildFile; fileRef = 437DE71945F67DF09BAC5E5BB65C5A20 /* OCMBlockCaller.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
8E596CBDA745237A010AE6C43429630D /* NSObject+RACKVOWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 8536C813226AC2CB3B45AB654F52A79D /* NSObject+RACKVOWrapper.m */; };
8FF7B6477BFA6E6ABA168E1417291D5F /* MASCompositeConstraint.m in Sources */ = {isa = PBXBuildFile; fileRef = 59D821E26E6C5AFE07427222FB4AB639 /* MASCompositeConstraint.m */; };
9027CF29E44F82D4CE33CB554C662F1B /* NSObject+RACDeallocating.m in Sources */ = {isa = PBXBuildFile; fileRef = B4250F1A74D43D858FA5327444397E4B /* NSObject+RACDeallocating.m */; };
906DCE66CD5BD236081D468616199BB7 /* SDWebImageOptionsProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 72C6CF5849F4A490EF85880EED97E773 /* SDWebImageOptionsProcessor.m */; };
913AD2CA2F03CE4F360AB192307AA9BB /* NSURLConnection+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 3E461ADDAEAAFCFADE347E2C9267118A /* NSURLConnection+RACSupport.m */; };
919576C1B49D8D1790FB53BBDC0B289E /* TyphoonRuntimeArguments.m in Sources */ = {isa = PBXBuildFile; fileRef = 3FE3393A74BB6D586E4B892A33322286 /* TyphoonRuntimeArguments.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
91AAF555B286FBF53E4F98D092B406BD /* SDWebImageTransitionInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 4BFF4254094763357CA3C84E0939A2C3 /* SDWebImageTransitionInternal.h */; settings = {ATTRIBUTES = (Private, ); }; };
91E8B94F8E02ABF5197DF5AE7D0B3934 /* SDWebImageDownloaderDecryptor.h in Headers */ = {isa = PBXBuildFile; fileRef = E8412B691818531EBFB6124AD9FDA943 /* SDWebImageDownloaderDecryptor.h */; settings = {ATTRIBUTES = (Public, ); }; };
928371B066E1211CE87089668D5BCB4C /* SDDiskCache.h in Headers */ = {isa = PBXBuildFile; fileRef = E0C543AB14CD3CB33002A31DFA8A3ED7 /* SDDiskCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
92B43E6B9457B4CAC5D92E7576C6D137 /* TyphoonInjectionByDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 70B4C8AEAC9F4A07E37287417595490E /* TyphoonInjectionByDictionary.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
9345137ED10358B60E37D05FB6165759 /* SDFileAttributeHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E12AEC65EEC09FD15C652679375036E /* SDFileAttributeHelper.m */; };
9345A406A970313436762F4127B84249 /* TyphoonInjectionByReference.h in Headers */ = {isa = PBXBuildFile; fileRef = 218826C133BB15ACA97ED4D92CBF2243 /* TyphoonInjectionByReference.h */; settings = {ATTRIBUTES = (Public, ); }; };
936352E7D044088C175A70DEB26D051A /* TyphoonTypeConverterRegistry.h in Headers */ = {isa = PBXBuildFile; fileRef = D3A21103401B7FEA00216399FCE680EE /* TyphoonTypeConverterRegistry.h */; settings = {ATTRIBUTES = (Public, ); }; };
93E07C2516E34149D3E8946A095F41ED /* TyphoonCallStack.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BC61D1DBB79E431F7B1D23F12DBA78A /* TyphoonCallStack.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
93FB8EE5015052DF64C1503CEB3B5233 /* TyphoonGlobalConfigCollector.m in Sources */ = {isa = PBXBuildFile; fileRef = CF7C5602AF31F940B921D40619F7D228 /* TyphoonGlobalConfigCollector.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
94633A6E95CF8D2DCB8D84EA25822024 /* TyphoonDefinitionRegisterer.h in Headers */ = {isa = PBXBuildFile; fileRef = F9523376659FAD01D3AAF8C11D821E14 /* TyphoonDefinitionRegisterer.h */; settings = {ATTRIBUTES = (Public, ); }; };
9470CC4CA104628FE6616BDC7AC04062 /* RACEagerSequence.h in Headers */ = {isa = PBXBuildFile; fileRef = 945289B9A84910C8F31646C46C4531EB /* RACEagerSequence.h */; settings = {ATTRIBUTES = (Public, ); }; };
94825DA7AAEA8838DD00AE6C2FA11124 /* UIDatePicker+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 201545627F7DB82D38F76A51D3D4C33F /* UIDatePicker+RACSignalSupport.m */; };
948E07C0F0FD61BD378ED66D5565D40F /* TyphoonDefinition.m in Sources */ = {isa = PBXBuildFile; fileRef = 35F7DE05C6E6BA99060F8F264A3CEBEA /* TyphoonDefinition.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
95621F7EE7F56B1ACF4141E6EFB175A8 /* GCDAsyncUdpSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 10396C86F2BA403403FECC4E3914CF31 /* GCDAsyncUdpSocket.h */; settings = {ATTRIBUTES = (Public, ); }; };
95DF4BD28B81E167A3AC2B7629A561AB /* RACQueueScheduler+Subclass.h in Headers */ = {isa = PBXBuildFile; fileRef = 9AA886724CF6656F190D039297F38E78 /* RACQueueScheduler+Subclass.h */; settings = {ATTRIBUTES = (Public, ); }; };
96E97174F4614FFA0649085022CB4AFE /* SDWebImage-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 576D3C34B2B7AC921593D4F072ECCBA3 /* SDWebImage-dummy.m */; };
97235408E59E16C18B6BDA1D29E1CB26 /* SDWebImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DF6104CF591EBBA4948511163C719CDB /* SDWebImageManager.m */; };
97385A64CA020489951EF769392C6DCF /* UIView+WebCacheOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = A4F5A7AB017EF810CFD76454F977D8A0 /* UIView+WebCacheOperation.m */; };
9744E7851D6BCCDD20453B7D9FC86A02 /* RACCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 74FEA9E372A4AFA285192EC20BF9AC10 /* RACCommand.h */; settings = {ATTRIBUTES = (Public, ); }; };
97694B5A3E6FC632B648D23D314AF28C /* NSString+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 8D7A7568A955352E3C8BEA8C6BE5EFA7 /* NSString+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
97C5A66B195BD0F9E7468F6C561CB4C7 /* NSObject+YYModel.h in Headers */ = {isa = PBXBuildFile; fileRef = C3133631DC229F9194ABFF320BE45553 /* NSObject+YYModel.h */; settings = {ATTRIBUTES = (Public, ); }; };
9838965B6E76EAC0A8EC90802D1A064B /* RACIndexSetSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = F069B23BF8F0BF5B39AAFB166C957FA3 /* RACIndexSetSequence.m */; };
98BA98A7102962139D85FF53B6F66E66 /* UIViewController+TyphoonStoryboardIntegration.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F6718C9B74C90CF46E2131FD2FB6517 /* UIViewController+TyphoonStoryboardIntegration.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
9993EEC4BDD2BB2437898147FCCE0B39 /* TyphoonViewHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 5A2D575E1ACB5A76D8F5214B6B4B171C /* TyphoonViewHelpers.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
99FBE73B7B7E6DA785FAD8ED7A2EBB94 /* NSIndexSet+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 252FA21B0D96A56E65E1481A1B27A6A3 /* NSIndexSet+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
9ADB69DEACB5B811F3CBF85ABF4C0F10 /* RACStream.m in Sources */ = {isa = PBXBuildFile; fileRef = 37CF1049FAEF879E02FD90463A2E3910 /* RACStream.m */; };
9B2813CB4372A381C770D86A6B8FEA43 /* NSNotificationCenter+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = F9B73C4A3EAA71EE3B8D1F790D768438 /* NSNotificationCenter+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
9B3420DEB8A0CCB9E1241A669AEFCA8E /* SDAnimatedImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EF30728B0B55C95AD32A7BC318AC70A /* SDAnimatedImage.h */; settings = {ATTRIBUTES = (Public, ); }; };
9B9343E8599EE5196BA75E842DCB48B7 /* NSBezierPath+SDRoundedCorners.h in Headers */ = {isa = PBXBuildFile; fileRef = 9E18DA0F8466E227AA17A93CF40D4C08 /* NSBezierPath+SDRoundedCorners.h */; settings = {ATTRIBUTES = (Private, ); }; };
9BF8077CFA9BBB10510FE915F2919A6E /* Typhoon.h in Headers */ = {isa = PBXBuildFile; fileRef = 203BC0FB7B1B96C82C4EAD4593D84A8D /* Typhoon.h */; settings = {ATTRIBUTES = (Public, ); }; };
9C034894E873E67C35BAF6879BF6F05A /* ReactiveObjC-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 5EB6B9FD0EAFE674F066B55BF02CBAD8 /* ReactiveObjC-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
9C05A169911849DC9E4DAFA0290A9100 /* OCMArg.m in Sources */ = {isa = PBXBuildFile; fileRef = D21426F24A160A03F3A3B57A03B3671E /* OCMArg.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
9CB0C910E0950794AF58F2F24D1D9D74 /* NSSet+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 11CA0DD35189B363533FA7DEBEAFC7E5 /* NSSet+RACSequenceAdditions.m */; };
9CE425B89294BE2C13E70A86E75B15CF /* SDDiskCache.m in Sources */ = {isa = PBXBuildFile; fileRef = A652A78922C1F9E12F183395B8233FDA /* SDDiskCache.m */; };
9DA8BF5E1F62665192ED18B27ADD0FBE /* UITableViewHeaderFooterView+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = CC5646C5FEED8AA9A5E6C5B7AA2A1AC0 /* UITableViewHeaderFooterView+RACSignalSupport.m */; };
9DB8E1A6396E3AF5AF0855353063ED40 /* NSData+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 5E4B3FE710833AB76C40A34DCAB075CA /* NSData+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
9DF446F8CA5BC4D4098766EC9063012C /* SDWebImageOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 1E5E520FF458D112BA579819CA973E7A /* SDWebImageOperation.h */; settings = {ATTRIBUTES = (Public, ); }; };
A046D9E46D0C8242D29D4365D8DBEC5A /* TyphoonViewControllerInjector.h in Headers */ = {isa = PBXBuildFile; fileRef = 7DB7EDBC439037E423096117CAA5C2D6 /* TyphoonViewControllerInjector.h */; settings = {ATTRIBUTES = (Public, ); }; };
A04BCE28D4B7FB41973BADA1BC666B7B /* OCMLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D5BC3205064C311F17DDCAF73FE5D91 /* OCMLocation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
A07B14D885F9954EC871BBB950DD65F9 /* TyphoonInjectionByDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 257F4F157F94CB40D1F5643758121C8F /* TyphoonInjectionByDictionary.h */; settings = {ATTRIBUTES = (Public, ); }; };
A096C06AA7D3C5C4E813C211767C3DDD /* TyphoonInjectionByObjectFromString.h in Headers */ = {isa = PBXBuildFile; fileRef = AD91A54AEE593AA59D16627F48452F75 /* TyphoonInjectionByObjectFromString.h */; settings = {ATTRIBUTES = (Public, ); }; };
A11E90132B32ABEA76703BAC68A86357 /* OCMStubRecorder.h in Headers */ = {isa = PBXBuildFile; fileRef = A139B74B3BD8E154D5BC6567D4D692D4 /* OCMStubRecorder.h */; settings = {ATTRIBUTES = (Public, ); }; };
A1560247914C760D9EE5F7A2392CC06C /* UIImage+GIF.h in Headers */ = {isa = PBXBuildFile; fileRef = 5E23710C44440556A6042E7F7D8E172B /* UIImage+GIF.h */; settings = {ATTRIBUTES = (Public, ); }; };
A1BD95BC04A46F6587569C6291351595 /* TyphoonViewControllerFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 330097FBD5A9529B89BA11A61293AD4C /* TyphoonViewControllerFactory.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
A2CC61E4BDAEBF249FCD573057423A7A /* TyphoonInjectionByCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = 661239F2EA786F002F7624EEE9A6FB7D /* TyphoonInjectionByCollection.h */; settings = {ATTRIBUTES = (Public, ); }; };
A2CF6FBE8187A1AF97E9995C6F119354 /* TyphoonAssemblyAccessor.h in Headers */ = {isa = PBXBuildFile; fileRef = B0E7E234958AD2EE1C16F6F2EA4B24FB /* TyphoonAssemblyAccessor.h */; settings = {ATTRIBUTES = (Public, ); }; };
A34F832763C924A0911D1E7797533EF8 /* NSDictionary+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DC413ABFFB1444FE469D466ADE4A98C /* NSDictionary+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
A3716B6B67727A527D76218D7A8C4C0B /* RACCompoundDisposableProvider.d in Sources */ = {isa = PBXBuildFile; fileRef = F13BB06DB4576664D2BD21C5F4490F59 /* RACCompoundDisposableProvider.d */; };
A3FD061F6162EC8CC95E8523C4F8E414 /* TyphoonDefinition+InstanceBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 429AF24E6181CE01E2CB7642A29FBB6C /* TyphoonDefinition+InstanceBuilder.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
A4334E9632A3BD0817D4F02E5D056D08 /* Collections+CustomInjection.h in Headers */ = {isa = PBXBuildFile; fileRef = 818F3C6FD63BEF6B17D992E201321502 /* Collections+CustomInjection.h */; settings = {ATTRIBUTES = (Public, ); }; };
A4360119EC4A3CECAE7D5B4288F0FED4 /* TyphoonJsonStyleConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 12676821542EF4B94B6913AF583A7A69 /* TyphoonJsonStyleConfiguration.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
A451C30BD205FCAE86B24C0BF35ED465 /* TyphoonAssemblyDefinitionBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FA8209FE24832482CBDBD96568B60E3 /* TyphoonAssemblyDefinitionBuilder.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
A4CD275DADB3551201C2A05AD4BB269E /* NSEnumerator+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 59FB7D5724747D377FC8799D1133C4A5 /* NSEnumerator+RACSequenceAdditions.m */; };
A50833B08706004695F22D20FB258019 /* RACSubscriptionScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = AE9AC83D30952DBFFC1F9C714196F202 /* RACSubscriptionScheduler.m */; };
A543C4BC7F666F22D3912798D07C04D5 /* OCMConstraint.h in Headers */ = {isa = PBXBuildFile; fileRef = 5649D72ECEC9159D3DAA4B7051E947B9 /* OCMConstraint.h */; settings = {ATTRIBUTES = (Public, ); }; };
A583A30CEA07A0748B6D95D5D12FF5F2 /* UICollectionReusableView+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 2E4DF20E80C1D11D64201766B309D73A /* UICollectionReusableView+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
A5C476EC387C41C840347804DF32CAF6 /* TyphoonInjectionDefinition.h in Headers */ = {isa = PBXBuildFile; fileRef = 9301B5A21675C37DED58F6472417FFF1 /* TyphoonInjectionDefinition.h */; settings = {ATTRIBUTES = (Public, ); }; };
A5F84EDD2763BADFD187D4E4C4043B95 /* NSObject+TyphoonIntrospectionUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = FA664986925B201863730F10AB76F9D5 /* NSObject+TyphoonIntrospectionUtils.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
A6B6F63BCC3A8BE45E9D30DF50935E0D /* OCMRecorder.m in Sources */ = {isa = PBXBuildFile; fileRef = C0225799E4F72C1CA22CAA520A08E589 /* OCMRecorder.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
A6FCF74348D1907891FBDE3F2E331136 /* DZNEmptyDataSet-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 3060A3BFE9758FA021148046B85E8CE5 /* DZNEmptyDataSet-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
A754D259316F9E237ABCA70AD7120758 /* TyphoonStartup.h in Headers */ = {isa = PBXBuildFile; fileRef = A13902D081C57317FEA1F09843D569F4 /* TyphoonStartup.h */; settings = {ATTRIBUTES = (Public, ); }; };
A79648E20089DBD6BF599C9F6B6570CE /* UIView+TyphoonOutletTransfer.h in Headers */ = {isa = PBXBuildFile; fileRef = 7CF6453EBAFEAB28E5DCF1E0FEA52A59 /* UIView+TyphoonOutletTransfer.h */; settings = {ATTRIBUTES = (Public, ); }; };
A826A5E0505880B690A0B1877D27CF17 /* NSArray+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = DF3C6B3C4596923C4E111113DF611DF6 /* NSArray+RACSequenceAdditions.m */; };
A839428F403C52D8AA3466B65E20C27A /* NSButton+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F69FEE7B67F930E4BAB806C41EDBDE5 /* NSButton+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
A8B06CAB8D3C0F81058B689563F2F6D2 /* OCPartialMockObject.h in Headers */ = {isa = PBXBuildFile; fileRef = C8A5867815E6437C1B59CDE852811716 /* OCPartialMockObject.h */; settings = {ATTRIBUTES = (Project, ); }; };
A8E58BF4665F9CC4142EA41D80CD6E17 /* Reachability-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = AFDA4AF26CABDC552E1905C902431F4F /* Reachability-dummy.m */; };
A9031D4C7A39A8F0CC9B4BB38D378A33 /* OCClassMockObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AC7882AF54A36E3A226CA3878D872F7 /* OCClassMockObject.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
A9071F54289564382DCD37D03E09EA0A /* RACSignalProvider.d in Sources */ = {isa = PBXBuildFile; fileRef = 89FE3BF0E622ACD1D48ABA7DCFF7855A /* RACSignalProvider.d */; };
A92AB5E65CA85947368E46E6627F1BFB /* UIButton+WebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = F817EA9532FEA2C318FD55D4CFE8D722 /* UIButton+WebCache.m */; };
A9A49E4A3BE8882F60DF32BAF39DE191 /* SDWebImageManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B18C13FE0EC5A5F756BBA99DC8559F1 /* SDWebImageManager.h */; settings = {ATTRIBUTES = (Public, ); }; };
AA14FA452C6E6DCB9D55BEC86A91ECB2 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C6506E5BA0D201F5A9633E8253219386 /* SystemConfiguration.framework */; };
AA1EA8F0F0470F1596B1FFA58ABF3375 /* SDWebImageDownloaderOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 84E53FAE993E1AC61F28D1CDC8FF68BC /* SDWebImageDownloaderOperation.m */; };
AA53B6ED50FD6066CC3536EB4C571E0C /* TyphoonAssemblyPropertyInjectionPostProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = E59E27A8B1D09C1C222A67BFFD0845C6 /* TyphoonAssemblyPropertyInjectionPostProcessor.h */; settings = {ATTRIBUTES = (Public, ); }; };
AAA6AF968C3E92D1BCCBAD27C696628A /* TyphoonOptionMatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B33DB28F54E682CA83C889709720FB7 /* TyphoonOptionMatcher.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
AAFF52BFB654564326D45F7BC6C7C1A2 /* RACTestScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = D8C316292E777A36CEF459683AF45953 /* RACTestScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; };
ABCB80C4813C849FC93D57676820C907 /* SDImageCacheDefine.h in Headers */ = {isa = PBXBuildFile; fileRef = E9BFDB08B8D6909114F2B3520B986044 /* SDImageCacheDefine.h */; settings = {ATTRIBUTES = (Public, ); }; };
ABDF2546C46AA0359E468651D3CBF26F /* RACTupleSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = 48E883D9DB5DF435F64617A907DCD908 /* RACTupleSequence.m */; };
AC14E56ECA7A4980A8E1CA68E800B12C /* SDWebImagePrefetcher.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D4A2340AC32529239D0841BD00726B1 /* SDWebImagePrefetcher.h */; settings = {ATTRIBUTES = (Public, ); }; };
AD0342BE54652F978FD313F99B10EB84 /* TyphoonMemoryManagementUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D14066F28EE5838EBA4D6142544116D /* TyphoonMemoryManagementUtils.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
AE7B02645B8F769CA5F215EE8F7CC5B0 /* View+MASAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 467F746C89C8EA1B73E4F4C1D741B528 /* View+MASAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
AEB8926360324E2A84020EAD714E33E4 /* RACSubject.m in Sources */ = {isa = PBXBuildFile; fileRef = 749EDDB4FA8F282C297D2ED71E5E2A95 /* RACSubject.m */; };
AED62511435CB679D8E8AC227B4AD8BD /* TyphoonFactoryDefinition.h in Headers */ = {isa = PBXBuildFile; fileRef = 28B9BD69A27186895E63DB6A19650F73 /* TyphoonFactoryDefinition.h */; settings = {ATTRIBUTES = (Public, ); }; };
AEEC1FF7C7D8C9B92AD8350441EED206 /* NSInvocation+TCFInstanceBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DFCA73A433D742E4E61D2DF66334925 /* NSInvocation+TCFInstanceBuilder.h */; settings = {ATTRIBUTES = (Public, ); }; };
AEF7F926931CDC5DF0FA40597840B792 /* TyphoonCollaboratingAssemblyProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 8866DCC2E6B6A7335F7564DFAAA6323F /* TyphoonCollaboratingAssemblyProxy.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
AFBA4DFDAFB6506D5DC862AFB1B4347B /* UISlider+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AA45AFD4053DDC8999AD121B914CE27 /* UISlider+RACSignalSupport.m */; };
AFD1D411A9387DCEC6F0034653E23787 /* DZNEmptyDataSet-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = D3B2AC38C369630473925094DAF536F5 /* DZNEmptyDataSet-dummy.m */; };
AFE16FE9315D936E91EECD0184A88FC0 /* TyphoonAssembly+TyphoonAssemblyFriend.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CC43EDBD34D1A25DB41669E7650524E /* TyphoonAssembly+TyphoonAssemblyFriend.h */; settings = {ATTRIBUTES = (Public, ); }; };
AFFC8A6E89ABBDA38786D88EFAFACC90 /* TyphoonComponentFactory+TyphoonDefinitionRegisterer.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F86F06F204B5BACDAEBC5742F93220C /* TyphoonComponentFactory+TyphoonDefinitionRegisterer.h */; settings = {ATTRIBUTES = (Public, ); }; };
AFFCF22AB10FDB1BA56E318CF0FEEACE /* RACKVOChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = B61B3D89FE168025B8BCCE1DE7423DF5 /* RACKVOChannel.h */; settings = {ATTRIBUTES = (Public, ); }; };
B13C2503DD42AF17264AA8F1724C831F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D376F0D7BDE2FCC4DDC6743AFC8DDA9E /* Foundation.framework */; };
B1536B8A4A473A9779083C563063B9C8 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D376F0D7BDE2FCC4DDC6743AFC8DDA9E /* Foundation.framework */; };
B17D5FAAC4B83F94F3841700C4FADE59 /* Pods-iOS-Network-Stack-Dive-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = B755FFA45FCE441B80E378A091AA25F4 /* Pods-iOS-Network-Stack-Dive-dummy.m */; };
B244D4AF0F8115F666321FE05AAFAE75 /* OCMObjectReturnValueProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C3A7610419191166B2C17925674DE17 /* OCMObjectReturnValueProvider.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
B24A1E4D80FC605011440F6676C52EF7 /* TyphoonInjectionContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 9BFCB32C228CF4CC491157D1716E631D /* TyphoonInjectionContext.h */; settings = {ATTRIBUTES = (Public, ); }; };
B2704AFFC5CC053154839DB44924D255 /* SDImageCoderHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 8CC52EA095AA94DD387D255F058AF87F /* SDImageCoderHelper.m */; };
B297981C7C5C77B17BE03972BB55D946 /* TyphoonDefinition+Storyboard.h in Headers */ = {isa = PBXBuildFile; fileRef = 30BC8EBE209C30443BACDAAA1B7A4CC1 /* TyphoonDefinition+Storyboard.h */; settings = {ATTRIBUTES = (Public, ); }; };
B312C7154719371C49BCF8055BCC1BFF /* NSOrderedSet+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 2385FD3E31A6F83168DE9EFA6AC4A66F /* NSOrderedSet+RACSequenceAdditions.m */; };
B331CE2D3DEB461E738B886086A365F9 /* SDImageGraphics.h in Headers */ = {isa = PBXBuildFile; fileRef = FDF2495A0E39E684767BAF85F109189B /* SDImageGraphics.h */; settings = {ATTRIBUTES = (Public, ); }; };
B3BAE2216F20F91DBC4C3E3B105E077D /* RACCompoundDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = 06AEB99F8D2F4D764FE818A7D523C570 /* RACCompoundDisposable.m */; };
B3CF0FD8C5CAE5360ECF449DF19A4B73 /* NSObject+RACPropertySubscribing.h in Headers */ = {isa = PBXBuildFile; fileRef = 9998C36BB30E19196F7C6F7D1F3EF49B /* NSObject+RACPropertySubscribing.h */; settings = {ATTRIBUTES = (Public, ); }; };
B448686E41D49FAC044634DE4BACBF49 /* NSValue+OCMAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D4FB47B1FC93F629C32CDEE69E59AE4 /* NSValue+OCMAdditions.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
B47B9BB155E21E5FB4D01940758EB91D /* NSObject+OCMAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = E93FFD1537CD2D6332F9A080E750CAAE /* NSObject+OCMAdditions.h */; settings = {ATTRIBUTES = (Project, ); }; };
B49EB728B20A04676F4A4569A18C7515 /* OCMBlockCaller.h in Headers */ = {isa = PBXBuildFile; fileRef = 14EDB30D06B6BB66F89E7DC87C6F82CE /* OCMBlockCaller.h */; settings = {ATTRIBUTES = (Project, ); }; };
B4A055DE2DAA83775FEAD725E7E34F13 /* RACArraySequence.m in Sources */ = {isa = PBXBuildFile; fileRef = 28CC19C7A5B9A7C5439E55E906813635 /* RACArraySequence.m */; };
B4A3D389BDB1B0E149378182CC942228 /* YYClassInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 50A9EB17E5CD95FF47A7DC9D1F6FB3B4 /* YYClassInfo.h */; settings = {ATTRIBUTES = (Public, ); }; };
B4F231C5CBAB3D4A184699D0066E0E83 /* SDImageAWebPCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 31794A4FD83642DD83BC70B9F11CA23A /* SDImageAWebPCoder.h */; settings = {ATTRIBUTES = (Public, ); }; };
B55D6315E855BFCE88C9B27B0C278C4F /* RACKVOTrampoline.h in Headers */ = {isa = PBXBuildFile; fileRef = E6FF1599AA4D761C6A0D334AC8A18BB7 /* RACKVOTrampoline.h */; settings = {ATTRIBUTES = (Public, ); }; };
B562AF7AAA4ACBCB972C87C4A5150D12 /* TyphoonBlockDefinition+InstanceBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = E9CD0BC00D291194A3441C33CBA01647 /* TyphoonBlockDefinition+InstanceBuilder.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
B59E60FBC9665FC1061B88B8E6FD9FAF /* Masonry-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 48A63D2EDED811435B879E1A51A2C5C3 /* Masonry-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
B5AF87C11A465F666473F6191D173905 /* UIView+WebCacheOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 41908C1E64673E808673215A63FCEC45 /* UIView+WebCacheOperation.h */; settings = {ATTRIBUTES = (Public, ); }; };
B5B4DEF5ED5485C7F0212CF50C06F191 /* OCMExpectationRecorder.h in Headers */ = {isa = PBXBuildFile; fileRef = E1D2B79FD0B5E7ECF99452AC0439FB6D /* OCMExpectationRecorder.h */; settings = {ATTRIBUTES = (Project, ); }; };
B6422D1C5378A1E8CF1DB4EA21D31BD3 /* RACErrorSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = F028FB003652E9875D30DDAF99C21162 /* RACErrorSignal.h */; settings = {ATTRIBUTES = (Public, ); }; };
B66356D4E7E43B3D15324569AA7EBB05 /* SDWebImageDownloaderOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 79AE668D1D91F91A64F0E74D1BA417EF /* SDWebImageDownloaderOperation.h */; settings = {ATTRIBUTES = (Public, ); }; };
B680C2604BD8BC9644AE7C67BC46B9BB /* MASLayoutConstraint.h in Headers */ = {isa = PBXBuildFile; fileRef = 6296945F878C9C5DD60D6B36B3DF4E9C /* MASLayoutConstraint.h */; settings = {ATTRIBUTES = (Public, ); }; };
B741DBE2A466E6211F879EF997D9322D /* SDImageCodersManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 217EBAD9071176F8750E350BA9867152 /* SDImageCodersManager.h */; settings = {ATTRIBUTES = (Public, ); }; };
B7E49AB762D1E2F7EA004FA2EEBD85A5 /* TyphoonDefinition.h in Headers */ = {isa = PBXBuildFile; fileRef = 4834BC1E84F882D9846062878F9F66C9 /* TyphoonDefinition.h */; settings = {ATTRIBUTES = (Public, ); }; };
B86B6B95CC8EE542367125BF22968812 /* TyphoonAssembly.m in Sources */ = {isa = PBXBuildFile; fileRef = 8D8C5527AA1D03083903623D593614BE /* TyphoonAssembly.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
B8E83660C660C63DD95B7C25B3C49DC5 /* NSOrderedSet+RACSequenceAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 0A69C05290E3D199301DB5557505AEEB /* NSOrderedSet+RACSequenceAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
B92BD83C69F10179ECCBF07FE6132768 /* RACScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = 027098AE8C5EB84C31AACA284246D555 /* RACScheduler.m */; };
B95C63A039D9D08896421291DEBD3AEB /* SDWebImageCacheKeyFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 146B7F55E58BD9E7C1CB0B3944357F9C /* SDWebImageCacheKeyFilter.m */; };
B995464A5578FA477280532E2C42C72E /* TyphoonInjectionsEnumeration.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A37BECA26F996876B784EFD357DC96A /* TyphoonInjectionsEnumeration.h */; settings = {ATTRIBUTES = (Public, ); }; };
B9B1C853AA90EF26C521CA880113A050 /* TyphoonPathResource.h in Headers */ = {isa = PBXBuildFile; fileRef = F0055EAC2B8C580C32290969DFF19220 /* TyphoonPathResource.h */; settings = {ATTRIBUTES = (Public, ); }; };
BA7F55364F2525C6B8AEA477AF3C8D67 /* TyphoonConfigPostProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = E1372334A95BBBD06EC3A3CDDE782AC3 /* TyphoonConfigPostProcessor.h */; settings = {ATTRIBUTES = (Public, ); }; };
BA904ABA8ED36CC4E5EB2B2004CA1F18 /* MASCompositeConstraint.h in Headers */ = {isa = PBXBuildFile; fileRef = 903891AB3F9D2ABE990A52BF6D7CE724 /* MASCompositeConstraint.h */; settings = {ATTRIBUTES = (Public, ); }; };
BADA31750A2136D073EDA4461DBE1EEA /* UIButton+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = EF2113E6C3216757EFC73B0ACD0ABD90 /* UIButton+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
BBA7D6B1F5A85263992FEDA1FF97BA10 /* NSObject+RACSelectorSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = 45512E8214358C10C4B33C4B78C7026F /* NSObject+RACSelectorSignal.h */; settings = {ATTRIBUTES = (Public, ); }; };
BBF48D90A3AC8570E5336E819EFE2F0A /* TyphoonInjectionByConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 71CC76D142ADC81938ACEAA26EA01492 /* TyphoonInjectionByConfig.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
BC60AF43B11C3B3BEA8E91CD67B3EB0C /* UISegmentedControl+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 646DB1C71B2294140046C6270BDC8C22 /* UISegmentedControl+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
BCDC1E1D46DD124B5726A064D2EE66A3 /* UIImage+MultiFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D09E947A17A03E7F1BE82DA4451726B /* UIImage+MultiFormat.m */; };
BCEFDE57BB0E0B36731C8D39FFA1BE2C /* SDWebImageDownloaderRequestModifier.h in Headers */ = {isa = PBXBuildFile; fileRef = AB64B8DD79975FE71CC74B5FBD9785F0 /* SDWebImageDownloaderRequestModifier.h */; settings = {ATTRIBUTES = (Public, ); }; };
BDBE494BAC544843982C3CA96A6C41DD /* SDAnimatedImagePlayer.h in Headers */ = {isa = PBXBuildFile; fileRef = A7FF20A852BCFA4897183BC64A6E1D00 /* SDAnimatedImagePlayer.h */; settings = {ATTRIBUTES = (Public, ); }; };
BED810910D0734E1A3D77CC7A4B7B08F /* TyphoonStoryboardDefinitionContext.h in Headers */ = {isa = PBXBuildFile; fileRef = A499F0CFC3E7B3B36E9786BCB6052F37 /* TyphoonStoryboardDefinitionContext.h */; settings = {ATTRIBUTES = (Public, ); }; };
BF22D137EF6324675FA50080C5D93C00 /* NSArray+MASAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = B5261A9D35AA9978FA8E8427895CDA41 /* NSArray+MASAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
BF560791320154400F8183101750BAEF /* TyphoonSelector.h in Headers */ = {isa = PBXBuildFile; fileRef = 17B387B9415B8C98C3579629FB677E97 /* TyphoonSelector.h */; settings = {ATTRIBUTES = (Public, ); }; };
BF7D73BC31ED41C8C87A7AD8E615BA90 /* UITextView+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 312F34A0D29AA628F2B731F4DA50B1E7 /* UITextView+RACSignalSupport.m */; };
BF7E2E36C374B7B1CD0A5332F2A9B26E /* RACKVOChannel.m in Sources */ = {isa = PBXBuildFile; fileRef = E3CC05377476E8CA3BDD23296111A57A /* RACKVOChannel.m */; };
BFBC117B8B5FE76AECB2448980618552 /* TyphoonBlockDefinition.m in Sources */ = {isa = PBXBuildFile; fileRef = D5A65F1C73D00CF1864BB3078888AECD /* TyphoonBlockDefinition.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
C029772A70ECFC78C192D0DED7EB10A5 /* TyphoonViewControllerInjector.m in Sources */ = {isa = PBXBuildFile; fileRef = BDD4238ED758F069DED59626A8859F28 /* TyphoonViewControllerInjector.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
C051A259A6CC3FF96D942F78A70AD43D /* TyphoonDefinition+Config.m in Sources */ = {isa = PBXBuildFile; fileRef = 40D4E59B9A952E2A8649507C8627874B /* TyphoonDefinition+Config.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
C08752A16103BCA4C7FD459F2D2CAF52 /* TyphoonUIColorTypeConverter.h in Headers */ = {isa = PBXBuildFile; fileRef = 32939C9AD7B94007A62205D5A5CA486C /* TyphoonUIColorTypeConverter.h */; settings = {ATTRIBUTES = (Public, ); }; };
C08F788A609C4CB2D525FD21D4FAD291 /* NSObject+RACKVOWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 633F2FE6EBD79D22A6B840F443F0A83B /* NSObject+RACKVOWrapper.h */; settings = {ATTRIBUTES = (Public, ); }; };
C1C54E804EC953B610851FBD38319CC6 /* TyphoonLoadedView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1F38003E7B8D270C5FC263FAC5B717D5 /* TyphoonLoadedView.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
C1CE7DDDEF51510CEDBE3B0C57A49FA3 /* NSString+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 3290EEA634B358879EBD921ACF1B1422 /* NSString+RACSupport.m */; };
C1DD8C6A64F948E4C53560C76B995DA4 /* SDAnimatedImageView.h in Headers */ = {isa = PBXBuildFile; fileRef = 0845491EC6BC3A5BFF1808F961A5440F /* SDAnimatedImageView.h */; settings = {ATTRIBUTES = (Public, ); }; };
C1ED07F75CD8C2D19DA79D96EA77F802 /* RACUnarySequence.m in Sources */ = {isa = PBXBuildFile; fileRef = 44725BC3ADC3FA22EF1DD0F3164C12FF /* RACUnarySequence.m */; };
C2068AEACC2D9C7F1FFE41AA25B12A68 /* MASUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 6622E7C87393460832DC95149834E273 /* MASUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; };
C21BB4F85EC11E3A16DB3EFD10168CD9 /* RACCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 551D5C997519E15B230BE54573BA8B99 /* RACCommand.m */; };
C22FE4A2C5BCF1C1BE001EC38B21A720 /* TyphoonDefinition+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 424E4FDDC999763B5ECDE171AF2948A3 /* TyphoonDefinition+Internal.h */; settings = {ATTRIBUTES = (Public, ); }; };
C237DF03B34507F0E0E883D8F7D242BB /* TyphoonAssemblyPropertyInjectionPostProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = E4338428A593399BDE88E88C9CEF385C /* TyphoonAssemblyPropertyInjectionPostProcessor.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
C2840BF1950FF7EE2DCD6D55F768A49C /* UIImage+GIF.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A4EBA3FAF480391CADE1DF78A1EB2DA /* UIImage+GIF.m */; };
C2DE26C06C3E3FE7C2D67DFF8F1B4565 /* TyphoonMethod.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D46806A2CE11059C3A8BD7E50783943 /* TyphoonMethod.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
C2FE60A10C792613E45031AE6E851ECB /* MASViewConstraint.m in Sources */ = {isa = PBXBuildFile; fileRef = 4349B8FF1BD06C628405857A06C179DD /* MASViewConstraint.m */; };
C449FE8D987CA3477F9F9272BF091CCE /* TyphoonNSURLTypeConverter.m in Sources */ = {isa = PBXBuildFile; fileRef = FFD16E6D61E5F1AA7ACD347FD6E0120D /* TyphoonNSURLTypeConverter.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
C45BDBA1768C56781E48457C6543F6B2 /* RACBehaviorSubject.m in Sources */ = {isa = PBXBuildFile; fileRef = CBD6CB22BEFFD7B25F968C06E8168BE5 /* RACBehaviorSubject.m */; };
C5B5337075E0DB3B2369B4CF7ED68479 /* TyphoonWeakComponentsPool.m in Sources */ = {isa = PBXBuildFile; fileRef = B66C84A540CE194D4B1F225949E9EA7F /* TyphoonWeakComponentsPool.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
C5F712FC268AE177C29C28244AADEF81 /* NSURLConnection+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 98A3E71FFFC45DA5BB85A8525B39AC52 /* NSURLConnection+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
C605731D421EA67B3D573E9C3E7C10CF /* TyphoonInjection.h in Headers */ = {isa = PBXBuildFile; fileRef = DAF562B23684D41ED34CC07CD14C7875 /* TyphoonInjection.h */; settings = {ATTRIBUTES = (Public, ); }; };
C6A100159974349FEAAC99B82BE0F872 /* SDImageLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = D029DA41DE158F6663F423C5BEC68F57 /* SDImageLoader.h */; settings = {ATTRIBUTES = (Public, ); }; };
C6E08EB55D40EC7D87E27012DA80C041 /* TyphoonViewControllerNibResolver.m in Sources */ = {isa = PBXBuildFile; fileRef = 645F110D573A95F0F5F61F5666D9AD90 /* TyphoonViewControllerNibResolver.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
C814E15114F4A34E55A3C23173F05537 /* TyphoonInjections.m in Sources */ = {isa = PBXBuildFile; fileRef = 98D4BFAFED921E3A451A3FCC58BD5A0A /* TyphoonInjections.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
C857B8D2D0BAA5A8A764F9E1C4B85807 /* ViewController+MASAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 92B2A700D6653C0C3364645A2E691CC0 /* ViewController+MASAdditions.m */; };
C8B284A4946540AF76FF866F9F34CF67 /* RACValueTransformer.h in Headers */ = {isa = PBXBuildFile; fileRef = 89A79290D890BC1A7E558C5CC31E06E3 /* RACValueTransformer.h */; settings = {ATTRIBUTES = (Public, ); }; };
C8EC35DFB0945DBE2F2FF9ECFE6D9711 /* NSLayoutConstraint+MASDebugAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DB7BACA057ADD843741CE2BDFDF62C8 /* NSLayoutConstraint+MASDebugAdditions.m */; };
C8FED80BA5A32BEA3BE00F5359153540 /* TyphoonStoryboardDefinition.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E2EED42BB0AD4DB8FDE62F4D4047113 /* TyphoonStoryboardDefinition.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
C93E972E75F84674690300123984EC43 /* SDAssociatedObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 252B49984FBEE0DBF2DD1314B8D9B974 /* SDAssociatedObject.m */; };
C96D04C53EB024F556E0F13422EED07A /* TyphoonTypeConverter.h in Headers */ = {isa = PBXBuildFile; fileRef = 2E59442B228F5A36B8D5592A675C8879 /* TyphoonTypeConverter.h */; settings = {ATTRIBUTES = (Public, ); }; };
C9E19D164C26414115CC969ED9A303C1 /* MASLayoutConstraint.m in Sources */ = {isa = PBXBuildFile; fileRef = F867F81312FCE227A0925280E5C9263C /* MASLayoutConstraint.m */; };
CA1E0DCDF679EA2DE2ED0915426E1D04 /* SDWeakProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = D07B2E954DF932CCE682BD41F588F5BA /* SDWeakProxy.m */; };
CB6E35960C294DA751F679E953F4F14F /* RACEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = CFCB3855AE45E56DBEA7833054EAD44F /* RACEvent.h */; settings = {ATTRIBUTES = (Public, ); }; };
CB7FD8FA81234BF9CC43527FE6260F62 /* RACReturnSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = 229B6EF02232FF52DA22913B1FB01A1F /* RACReturnSignal.m */; };
CBAAE2A674699D3391C898EA00D0AA5C /* RACSubject.h in Headers */ = {isa = PBXBuildFile; fileRef = A28930506531365373875AD4CA53EDEC /* RACSubject.h */; settings = {ATTRIBUTES = (Public, ); }; };
CC4630C63CAEBBA70AF10E01626A5D30 /* UIResponder+TyphoonOutletTransfer.h in Headers */ = {isa = PBXBuildFile; fileRef = D41D80BBE2F576947F11C3B0CDCDB170 /* UIResponder+TyphoonOutletTransfer.h */; settings = {ATTRIBUTES = (Public, ); }; };
CD2D5C2B71BF29BC796199C6A4960927 /* NSObject+OCMAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 38F730D07E4C4D067A10C1C091A76D44 /* NSObject+OCMAdditions.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
CD7113EF56D2E1D58FC49C10330BD6BE /* OCLogTemplate.h in Headers */ = {isa = PBXBuildFile; fileRef = AAB807AFFC7C3B79795D0CA6C280CC13 /* OCLogTemplate.h */; settings = {ATTRIBUTES = (Public, ); }; };
CD923193903F98B2F7CD0354C2116417 /* TyphoonCircularDependencyTerminator.h in Headers */ = {isa = PBXBuildFile; fileRef = 0CB61254C5C3C9FFDE2C23A595DB093C /* TyphoonCircularDependencyTerminator.h */; settings = {ATTRIBUTES = (Public, ); }; };
CDB274EDA02524CBBF99EEE217AE4505 /* TyphoonOptionMatcher+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 468798856EED0D94D6E982B4D271195A /* TyphoonOptionMatcher+Internal.h */; settings = {ATTRIBUTES = (Public, ); }; };
CE2CD6090ADB66ED21B0800C527D7B02 /* TyphoonInjectionByType.m in Sources */ = {isa = PBXBuildFile; fileRef = 3CD424DC6ECC7B41C2BAB51078B5FB8E /* TyphoonInjectionByType.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
CF26C843968B8CBFF3931E82CA77FB81 /* RACKVOTrampoline.m in Sources */ = {isa = PBXBuildFile; fileRef = 871E83256238E28BEC109FBAA903A7BE /* RACKVOTrampoline.m */; };
CF2E818EBCD9D8AE88B891F33C042B46 /* TyphoonResource.h in Headers */ = {isa = PBXBuildFile; fileRef = ECBE53D68EFF65EB7F6020F1BE9C5B3B /* TyphoonResource.h */; settings = {ATTRIBUTES = (Public, ); }; };
CF96B3FEA3AEFCF6137DD7A22D92569E /* YYModel-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A1ED1A10CBE44A4F0190574BE33E602 /* YYModel-dummy.m */; };
CFF8D1A5E4C2097EF05E1021FE112886 /* SDWebImageIndicator.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D81FCE9D11F4054F824AE0AD9C905D9 /* SDWebImageIndicator.m */; };
D03A07BAF17F4E9CF1D2D3401AB1CF4B /* TyphoonStoryboard.m in Sources */ = {isa = PBXBuildFile; fileRef = A7EF786049567EB4166E1062885CCF21 /* TyphoonStoryboard.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
D06BB547D59D183FD1DDD84DEBAC9EE8 /* SDWebImageCacheSerializer.m in Sources */ = {isa = PBXBuildFile; fileRef = B96D3683B3E4B884909B07397CF43A5D /* SDWebImageCacheSerializer.m */; };
D0959AA30897EC5180B57C59FFA10849 /* OCMBoxedReturnValueProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 1C87CAE8BB86399A29C7AAFACF0CD24D /* OCMBoxedReturnValueProvider.h */; settings = {ATTRIBUTES = (Project, ); }; };
D0A8F86C31CF8BE5C61785EF585EE6A2 /* RACDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = B9B7D7D1F5CDB012C8AEE5F8EF3F513D /* RACDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; };
D0DA377AA5FF23BEB89614E300E90D2D /* TyphoonMemoryManagementUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 9858D4D21F87468E116E7410167A453A /* TyphoonMemoryManagementUtils.h */; settings = {ATTRIBUTES = (Public, ); }; };
D0DD94C9BBAF8C653232930C3C3F2F73 /* NSObject+RACLifting.h in Headers */ = {isa = PBXBuildFile; fileRef = 5FAD9C753437FC1DCDDA7F7F6A8B9C29 /* NSObject+RACLifting.h */; settings = {ATTRIBUTES = (Public, ); }; };
D1D1BC57D17B127642A4A4DAFE50A033 /* NSInvocation+TCFUnwrapValues.m in Sources */ = {isa = PBXBuildFile; fileRef = C92A811BD719C9FA38BF1EB73409CA04 /* NSInvocation+TCFUnwrapValues.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
D1E674341E780EABFB25ECC1420B08A2 /* TyphoonBlockDefinition.h in Headers */ = {isa = PBXBuildFile; fileRef = 712ED30137ED21790E81D661BCDBE41A /* TyphoonBlockDefinition.h */; settings = {ATTRIBUTES = (Public, ); }; };
D287C76C50258D4C92FFDD2059168F45 /* RACSignal+Operations.h in Headers */ = {isa = PBXBuildFile; fileRef = 89570B4A4AE434196E38ABEADF0CB97E /* RACSignal+Operations.h */; settings = {ATTRIBUTES = (Public, ); }; };
D2AA8F17857AFD2BC5F384053D3FB619 /* NSObject+DeallocNotification.h in Headers */ = {isa = PBXBuildFile; fileRef = D179E0BB3A9462316E480E37EBCDDDF5 /* NSObject+DeallocNotification.h */; settings = {ATTRIBUTES = (Public, ); }; };
D2BB17CC67A51CF2396BEE012663441D /* TyphoonFactoryAutoInjectionPostProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = F06397AB33F7FA583810C154BB7C6BB2 /* TyphoonFactoryAutoInjectionPostProcessor.h */; settings = {ATTRIBUTES = (Public, ); }; };
D2CD8848F856EC9942A76610AAE66F0A /* SDImageIOCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 996F286581DA660FC5CB9DCBB431AF08 /* SDImageIOCoder.h */; settings = {ATTRIBUTES = (Public, ); }; };
D31E7D189E5A633875C9C723B5CCDECA /* TyphoonParameterInjection.h in Headers */ = {isa = PBXBuildFile; fileRef = 13BDADC3DD9D34511EDA118822EA790E /* TyphoonParameterInjection.h */; settings = {ATTRIBUTES = (Public, ); }; };
D397F3D9E12E75959BB21782CBFD8755 /* UIAlertView+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = B4C1BAAAE45FAB43E8ECC69C9DC35A7D /* UIAlertView+RACSignalSupport.m */; };
D3B29ED852413926E7B50A998517B1B5 /* TyphoonInjectionByObjectInstance.h in Headers */ = {isa = PBXBuildFile; fileRef = 83FC227B502F54235D6CCDB02449F168 /* TyphoonInjectionByObjectInstance.h */; settings = {ATTRIBUTES = (Public, ); }; };
D3BD1317C424DEBE3F14AA0F6B0F2D54 /* TyphoonInject.h in Headers */ = {isa = PBXBuildFile; fileRef = AA109A4B29ED94AC8CB0BB3D4626927E /* TyphoonInject.h */; settings = {ATTRIBUTES = (Public, ); }; };
D40F7E1DD6EA34E8F749A14B0CD9BE84 /* UIStepper+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B0A489E0C0397C62B216A44A21EDDFF /* UIStepper+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
D423726E8FA79619214170CEE328676A /* RACErrorSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = 5ACE3A2A33422C2FD382351D2E96A005 /* RACErrorSignal.m */; };
D49896323350314AAEE3F22E1556C3D0 /* NSInvocation+TCFWrapValues.m in Sources */ = {isa = PBXBuildFile; fileRef = 69EDFB408AB8C8E85794472FD278D3F4 /* NSInvocation+TCFWrapValues.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
D51E06FD6284B91EAC91FD874C101ABF /* NSInvocation+TCFCustomImplementation.m in Sources */ = {isa = PBXBuildFile; fileRef = 032501D9A06A7D0265ED238BBC7D25E3 /* NSInvocation+TCFCustomImplementation.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
D55F6AFEF9DB4F474DACB788A627AAC9 /* UIView+TyphoonDefinitionKey.m in Sources */ = {isa = PBXBuildFile; fileRef = 1592F539A623B93799CF4145741CFC4E /* UIView+TyphoonDefinitionKey.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
D5A4DE2104B36BC4B8FD6E7E5FC30938 /* TyphoonWeakComponentsPool.h in Headers */ = {isa = PBXBuildFile; fileRef = 22EF63276DBF66ABD2D5FBF76746A708 /* TyphoonWeakComponentsPool.h */; settings = {ATTRIBUTES = (Public, ); }; };
D5F07F7462A9AD80E4A9DB21294D8FA2 /* UIGestureRecognizer+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 43D883AF4752701FF9AD2DF1CF845589 /* UIGestureRecognizer+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
D62A672EEB252581BD972DDA862BE1DD /* SDWebImage-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 0453653DE2FEC83ACFBA5BA70EBEF291 /* SDWebImage-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
D649EB2BE8D16B1BB89E41CAA09C1702 /* TyphoonBlockComponentFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 75D795D038F45CC15803AC36BBFEBF32 /* TyphoonBlockComponentFactory.h */; settings = {ATTRIBUTES = (Public, ); }; };
D662C83ECE8BEDA5FFB52F3575CA3E1A /* SDImageCache.h in Headers */ = {isa = PBXBuildFile; fileRef = B913D23BDDEDDE62A0E2C3493A0E97F5 /* SDImageCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
D663837F4347AF58660EE6F7FD426ECE /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D376F0D7BDE2FCC4DDC6743AFC8DDA9E /* Foundation.framework */; };
D788BA4B9E8186271BA75CA52B30502C /* View+MASAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CDF6A89092579B8DC5007CD2E67B5C48 /* View+MASAdditions.m */; };
D7B3E8948DB04BD8FB6748419DA03EA9 /* SDAnimatedImageView+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 4F1747F10DA9479C6BDD54B652E1C545 /* SDAnimatedImageView+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
D7C50F17FD247E49530D1EF5D55EF4E5 /* OCMObserverRecorder.h in Headers */ = {isa = PBXBuildFile; fileRef = 0FC01820ACD24F5CF0969F06380ABFE9 /* OCMObserverRecorder.h */; settings = {ATTRIBUTES = (Project, ); }; };
D875F931575EA6C3308F17F716CC8748 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D376F0D7BDE2FCC4DDC6743AFC8DDA9E /* Foundation.framework */; };
D8B4CB8F113156A6CD75C060D0113BE4 /* NSInvocation+RACTypeParsing.h in Headers */ = {isa = PBXBuildFile; fileRef = 9CB472E36DC6E2E3FEF5B6638CA34EC5 /* NSInvocation+RACTypeParsing.h */; settings = {ATTRIBUTES = (Public, ); }; };
D92622FB36407A6E91CB2509480A7F89 /* TyphoonPreattachedComponentsRegisterer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F2869DEA989F62C012853D92680ECEC /* TyphoonPreattachedComponentsRegisterer.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
D94824EC234BFBFF814D88F9DD042BEA /* OCMObserverRecorder.m in Sources */ = {isa = PBXBuildFile; fileRef = F4403468E084E9FF3F896A3734CD95FB /* OCMObserverRecorder.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
D968461E31E8FF3FF6BA1DC621B0433B /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C5460C93594FE49E0DF49CC9F802F1F3 /* UIKit.framework */; };
DB6EF4B473D70F75543FA047C10E1178 /* TyphoonFactoryPropertyInjectionPostProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 90F23D124204BAE6E0746E042588193F /* TyphoonFactoryPropertyInjectionPostProcessor.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
DB7372889DFAB01A489C7A290AFFD9B2 /* TyphoonBundleResource.h in Headers */ = {isa = PBXBuildFile; fileRef = 94138F5AF19A8078AD96AA7CBA29AB4A /* TyphoonBundleResource.h */; settings = {ATTRIBUTES = (Public, ); }; };
DBA9500CBBA5FF6FCBBA115AE4D12152 /* NSLayoutConstraint+MASDebugAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 253F5F73A60CE9213B91A4027838C766 /* NSLayoutConstraint+MASDebugAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
DBDC0A614556A03F8950731FCB0B2B92 /* TyphoonDefinition+Infrastructure.h in Headers */ = {isa = PBXBuildFile; fileRef = 23D6C0C2DA8003ECC644C3366B6E5247 /* TyphoonDefinition+Infrastructure.h */; settings = {ATTRIBUTES = (Public, ); }; };
DC2FDB5C74828AB18BA20E051DF0C37A /* TyphoonDefinitionNamespace.h in Headers */ = {isa = PBXBuildFile; fileRef = 1EE6F568285A1219683FB22F70790F2A /* TyphoonDefinitionNamespace.h */; settings = {ATTRIBUTES = (Public, ); }; };
DD256ACFDD49A35B9747419FFAB3A287 /* TyphoonAssemblyBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 1466DD8719F4531C498D10CD285A8856 /* TyphoonAssemblyBuilder.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
DDA49C713DF9E0C1A9B1AD6A6F1DE610 /* TyphoonIntrospectionUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D31CBCD57F3773D4DC8B94E19806A4E /* TyphoonIntrospectionUtils.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0 -DOS_OBJECT_USE_OBJC=0"; }; };
DE2553DFC19AAB617B63261D50C44091 /* UISlider+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 38A1E514232587E860EC29C335D4DC68 /* UISlider+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
DE5CB39DB19EE9A2F044B3AD41BCE22D /* Reachability.h in Headers */ = {isa = PBXBuildFile; fileRef = C51A574AFE5E5029BD52D9AC1C54C204 /* Reachability.h */; settings = {ATTRIBUTES = (Public, ); }; };
DEA09692CF813A23899CD4949A9B6801 /* SDAnimatedImageRep.h in Headers */ = {isa = PBXBuildFile; fileRef = BE52EEDB31E3B6435F83E7E317F3047D /* SDAnimatedImageRep.h */; settings = {ATTRIBUTES = (Public, ); }; };
DF1F2CC29D9871EF48FCD47055CB2A59 /* UIImagePickerController+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = FFBE8455C54B78A685FE3CC5721FC439 /* UIImagePickerController+RACSignalSupport.m */; };
DF2B15402CE105F5A8CE48BBDCFFD5DD /* MASConstraint.m in Sources */ = {isa = PBXBuildFile; fileRef = 80E8E2470B296C3F1F4F779B79894FB3 /* MASConstraint.m */; };
DFC863A0F6A7902C35DC110A3CCBFE93 /* TyphoonCollaboratingAssemblyPropertyEnumerator.h in Headers */ = {isa = PBXBuildFile; fileRef = 572092422C1553EC0456C0BF53CDE8C2 /* TyphoonCollaboratingAssemblyPropertyEnumerator.h */; settings = {ATTRIBUTES = (Public, ); }; };
E075608ADB8A563BCA3D9F6C123220ED /* NSUserDefaults+RACSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = DAA9B1CEFE3B5F49253B19B6F13CBE00 /* NSUserDefaults+RACSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
E07A92468276197EE7EFACE1A4C7D456 /* OCMMacroState.m in Sources */ = {isa = PBXBuildFile; fileRef = 814AAFBE786E0EBCAFD7AC642DA8F8EF /* OCMMacroState.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
E0BCF21E9FA59F638C13ECCECC4D9690 /* SDMemoryCache.m in Sources */ = {isa = PBXBuildFile; fileRef = B534D8B84FDE7415642A1D01E3733910 /* SDMemoryCache.m */; };
E15319829D042A0E6BBE4005D44FACDA /* UIControl+RACSignalSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 707BB63597C3E112E47016299D9A2C40 /* UIControl+RACSignalSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
E1B4B95B63D486197DB87B5301B2AD68 /* TyphoonMethodSwizzler.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D9725591DAD7A11FB7BCF2AA89B402B /* TyphoonMethodSwizzler.h */; settings = {ATTRIBUTES = (Public, ); }; };
E25B7F7ED64E459D6BA4E9FA1B812370 /* MKAnnotationView+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 53B86BED43D4B74CCE944876C11E57B1 /* MKAnnotationView+RACSignalSupport.m */; };
E3DB273F0E910956E9DE80D085901ECE /* TyphoonBundledImageTypeConverter.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F05B9328BABBA34AFCE3172B61D1F83 /* TyphoonBundledImageTypeConverter.h */; settings = {ATTRIBUTES = (Public, ); }; };
E4E86D179162DB63010C0D8D68CF08FB /* TyphoonDefinition+Storyboard.m in Sources */ = {isa = PBXBuildFile; fileRef = 3E5BC7E6CAFF72A150840D4DC92D331D /* TyphoonDefinition+Storyboard.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
E4F1B478580D6D7328BC29607BDE46F6 /* UIImage+ExtendedCacheData.m in Sources */ = {isa = PBXBuildFile; fileRef = D1FE5339C49B5BC54F80CD72EDBB13EA /* UIImage+ExtendedCacheData.m */; };
E50613C67DD02AF6EA825DA0B31EFFAD /* SDImageGraphics.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DF1AA147C3C7EA2E20CEC9444F9EDF2 /* SDImageGraphics.m */; };
E528D9C6017F3966EDE09AE63D49B22E /* TyphoonComponentFactory+Storyboard.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F375B929B1442DA30D02F6A861124C /* TyphoonComponentFactory+Storyboard.h */; settings = {ATTRIBUTES = (Public, ); }; };
E58F464BF2649F7F2E4ED53CBFC5C466 /* OCMInvocationStub.h in Headers */ = {isa = PBXBuildFile; fileRef = B6C0AFCABBA43F288D26D6A9CA0DB2DA /* OCMInvocationStub.h */; settings = {ATTRIBUTES = (Project, ); }; };
E59439290BC7524578C0A0E9C06B7719 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = BCB7911698C2C8CF6B83F92DF10B11EF /* PrivacyInfo.xcprivacy */; };
E5AD74C206FCBD08FBBA2F359D787980 /* RACGroupedSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = 686988B863D5701A04411416EC7BF222 /* RACGroupedSignal.h */; settings = {ATTRIBUTES = (Public, ); }; };
E694501C0EFD2BDD91303F10CD877F31 /* TyphoonInjectionByComponentFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 0FFBFA01D44CE27E222D10DF44B1FBBB /* TyphoonInjectionByComponentFactory.h */; settings = {ATTRIBUTES = (Public, ); }; };
E76969F9B01139118427505B18F9CD21 /* SDImageHEICCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 5DD596766B816867DE3F36CD6A523540 /* SDImageHEICCoder.h */; settings = {ATTRIBUTES = (Public, ); }; };
E80BC1F3C11C00D429504583AEC7EB12 /* RACChannel.h in Headers */ = {isa = PBXBuildFile; fileRef = 246818BE8D07C34C5CC6113926619C5F /* RACChannel.h */; settings = {ATTRIBUTES = (Public, ); }; };
E81BE9935B2121C12CEC06480E77EA22 /* RACSubscriptingAssignmentTrampoline.h in Headers */ = {isa = PBXBuildFile; fileRef = 35811A877C1D60F1A3AB124ACBC29E9E /* RACSubscriptingAssignmentTrampoline.h */; settings = {ATTRIBUTES = (Public, ); }; };
E81F276ED056A35A0D7BB6CE9F9B26DC /* TyphoonAssemblyBuilder+PlistProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = AB7271D8A582657DF93DCFAD4F92FA7F /* TyphoonAssemblyBuilder+PlistProcessor.h */; settings = {ATTRIBUTES = (Public, ); }; };
E87077FBDFB98FEFBCD2EE88AC4B62A4 /* OCMBlockArgCaller.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D066AE9F0D7A4328D8690DF15F20AA0 /* OCMBlockArgCaller.h */; settings = {ATTRIBUTES = (Project, ); }; };
E8AB529B9E0B4C23921344F6C4ABFEA4 /* SDImageCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 82A6DCEB5EBCB25A5BF6375B4D2FE619 /* SDImageCoder.m */; };
E8F1A153D3063B75194913D03CA2D383 /* OCMockMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = EF447269480FB0580228D478883FFA69 /* OCMockMacros.h */; settings = {ATTRIBUTES = (Public, ); }; };
E930A5612DC6D120BE040AD17C6D1BCD /* MASViewAttribute.m in Sources */ = {isa = PBXBuildFile; fileRef = 2ADA8F4AE98D80692FA44970F3846684 /* MASViewAttribute.m */; };
E96F37E3D1E77EB2349FF06BD82E95E9 /* RACEagerSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = DB54DF3DB0FDADB1A3893CFF8C1AF03A /* RACEagerSequence.m */; };
E99D0FDFED6B9B6CE4F1373A8D4CEE03 /* RACSubscriber+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 2AE9080EF975331395E265C09E03252C /* RACSubscriber+Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
E9E16E749FD6121557C8D9E648A8C6AB /* RACDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = F8217C098AD84E361BF0AFBB60065247 /* RACDelegateProxy.h */; settings = {ATTRIBUTES = (Public, ); }; };
EA82B6D97C9C5D0558047AF552D63203 /* SDWebImageDefine.h in Headers */ = {isa = PBXBuildFile; fileRef = 702F65559FA8A65C2C9E8A7280D4E423 /* SDWebImageDefine.h */; settings = {ATTRIBUTES = (Public, ); }; };
EB27DEF6CC7E1606E358166B56931C05 /* NSObject+YYModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 7CC392EF120EE41995D6918D82EC742B /* NSObject+YYModel.m */; };
EB2B34DC4B6CA9FDEAF8EB5964D4EE3B /* OCMIndirectReturnValueProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 9752AAF7F49C7CFA8636AB119BF88C35 /* OCMIndirectReturnValueProvider.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
EC9B34262AED632D7EFB49804337648E /* Masonry.h in Headers */ = {isa = PBXBuildFile; fileRef = A20BC1B3BD8F0D819039127EE5346176 /* Masonry.h */; settings = {ATTRIBUTES = (Public, ); }; };
ECE64B732F9FA7C402DDEEC58DCB9D98 /* SDImageAPNGCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = B67521AEB3057A6B1FDF7908EB18E894 /* SDImageAPNGCoder.m */; };
ED8F64FF98CFAE0B12CF60A1B0E6BAF8 /* SDCallbackQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = A66DC1E844041574BF00E1D8870DE753 /* SDCallbackQueue.h */; settings = {ATTRIBUTES = (Public, ); }; };
EE19A56BB25AE31295A4D913C339FF02 /* TyphoonDefinition+Option.h in Headers */ = {isa = PBXBuildFile; fileRef = A146863CEB210DB61D7A021374982754 /* TyphoonDefinition+Option.h */; settings = {ATTRIBUTES = (Public, ); }; };
EE77A716DB37472592376BD26C7DDD78 /* TyphoonGlobalConfigCollector.h in Headers */ = {isa = PBXBuildFile; fileRef = 875B63A814EBB6CC52061388F53BC97D /* TyphoonGlobalConfigCollector.h */; settings = {ATTRIBUTES = (Public, ); }; };
EE8AE70BAD73F5BE3689FA838B23AFB7 /* OCObserverMockObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 36EA9831D45C8C2DBBE9C8924552B0F3 /* OCObserverMockObject.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
EEA86BBF0385C53CE0567D92C87C5E8B /* TyphoonDefinition+InstanceBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = C4FBBF6C2C66E9E078C222133056807B /* TyphoonDefinition+InstanceBuilder.h */; settings = {ATTRIBUTES = (Public, ); }; };
EF6A6C725598F572A70C5FCEE328C184 /* SDImageTransformer.h in Headers */ = {isa = PBXBuildFile; fileRef = 83044A60173A4F57FC96702D95EF4DB8 /* SDImageTransformer.h */; settings = {ATTRIBUTES = (Public, ); }; };
F0A541AAA195CE1E0DB03048E621CC0D /* OCMNonRetainingObjectReturnValueProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 357902381BACEC332590C847106F5C16 /* OCMNonRetainingObjectReturnValueProvider.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
F1454467D8C33378127057476591C1AF /* RACDynamicSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = 15CD57574E2617CB7558B0BD733BAFC2 /* RACDynamicSequence.m */; };
F16BF2164AA7A01C3C46E80BBEBB14AE /* NSValue+OCMAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 1D3495A662B6322B041706885A769B02 /* NSValue+OCMAdditions.h */; settings = {ATTRIBUTES = (Project, ); }; };
F18AB133D2D0F40618D0A4C87D3AF0BF /* RACBlockTrampoline.h in Headers */ = {isa = PBXBuildFile; fileRef = 25FCBE4E8D3998433D6BFA94FB2FAC69 /* RACBlockTrampoline.h */; settings = {ATTRIBUTES = (Public, ); }; };
F19558DCCE275BD72B143F8ACE45E7E0 /* OCMockObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 8375DB2B7CC0F5AD235F69805AF4B1BA /* OCMockObject.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
F2DAC1DEF200EB8CBBF570272D86CA51 /* NSInvocation+RACTypeParsing.m in Sources */ = {isa = PBXBuildFile; fileRef = FB44908A42BD6D708EAD4C6F7B6F3B5A /* NSInvocation+RACTypeParsing.m */; };
F341B1F0FEDE67D2DB40B5C5D854DA8A /* RACEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 861A84C50B238264389CE8E34734EE4C /* RACEvent.m */; };
F3604694FDE5476E5AA37DFDB131C7C8 /* NSNotificationCenter+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DBB238DBD9086C4A06C98A1C750EA43 /* NSNotificationCenter+RACSupport.m */; };
F3F7651923711E6A4A12FBE3B5DDCC4E /* NSString+RACKeyPathUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = FAD1079497F1714CF45F8A6BF851636D /* NSString+RACKeyPathUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; };
F40E7796D3A7339E16757A9B9A6A46E7 /* TyphoonTypeConverterRegistry.m in Sources */ = {isa = PBXBuildFile; fileRef = 906070901EFA9ECD175C85348FF57C1E /* TyphoonTypeConverterRegistry.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
F432C2EF2FDAE1EA39A56AA772932A38 /* NSInvocation+OCMAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 47E16F28A08ED8AA345A07CC51EAF129 /* NSInvocation+OCMAdditions.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
F465B45E09EC9F7F08BAC6735CD7124D /* NSObject+PropertyInjection.h in Headers */ = {isa = PBXBuildFile; fileRef = ADFAD562310FEB485867BE6D63C82804 /* NSObject+PropertyInjection.h */; settings = {ATTRIBUTES = (Public, ); }; };
F49CB22863CCFEC7817D259F27F91C57 /* SDWebImageIndicator.h in Headers */ = {isa = PBXBuildFile; fileRef = B478DF0CC09DF6605F4772947370CBE1 /* SDWebImageIndicator.h */; settings = {ATTRIBUTES = (Public, ); }; };
F5372060DD1B80D1DAB5ABA234F40C66 /* TyphoonStoryboardProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = AC040A20E5DFBE7A6827B92595B02C58 /* TyphoonStoryboardProvider.h */; settings = {ATTRIBUTES = (Public, ); }; };
F53BE4449AE5896F76325E4DCB6D0B13 /* SDImageCachesManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 5F41C49350454308C011F60E41C91F54 /* SDImageCachesManager.h */; settings = {ATTRIBUTES = (Public, ); }; };
F59065DC32765AD32444CEAE1B1E8250 /* TyphoonUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 94E380F27B3CBBA506348C94A1707E2E /* TyphoonUtils.h */; settings = {ATTRIBUTES = (Public, ); }; };
F5A6806DA01AEDEA925EF7F6F42A1BA5 /* NSFileHandle+RACSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = B54343D5BDC4CA5AB85FD35468187848 /* NSFileHandle+RACSupport.m */; };
F5D147CA9C21670C9C509D0C40C55015 /* OCMArgAction.h in Headers */ = {isa = PBXBuildFile; fileRef = F5CC2EA5805079A0B25919E89EAB7C84 /* OCMArgAction.h */; settings = {ATTRIBUTES = (Project, ); }; };
F5E9B7AD93F258223E150D81EC68291A /* OCMExceptionReturnValueProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E25BC9F9B2C745B6471F74CCB47DC7F /* OCMExceptionReturnValueProvider.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
F68889CD481716EE5D6B75EBD8FD53A6 /* SDImageCoderHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = F4CE8A12723ABBCC55A89EB52BEB7E84 /* SDImageCoderHelper.h */; settings = {ATTRIBUTES = (Public, ); }; };
F6D1C960368EB1E067ABD0BFF707FC56 /* MASConstraintMaker.m in Sources */ = {isa = PBXBuildFile; fileRef = 838C51303B89542DD2D308D0C408F616 /* MASConstraintMaker.m */; };
F775F679F321E234A469F54310116E6D /* RACValueTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 28B829B34223ADB0F04E28F63DEA2868 /* RACValueTransformer.m */; };
F7CA711927E14F963F5E47EEBC79EE62 /* RACKVOProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = CA677B07FC524F62109AE180874AB60E /* RACKVOProxy.h */; settings = {ATTRIBUTES = (Public, ); }; };
F84821217598572DCD6377D7E5AF7D27 /* RACSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = 2C69DF26B88BA2D74723697231D11733 /* RACSignal.h */; settings = {ATTRIBUTES = (Public, ); }; };
F8828C0B5037617E7C5EAB0F6499302E /* TyphoonInjectedObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 35D424AE41D9D5DF8B4CD98EE40E20E2 /* TyphoonInjectedObject.h */; settings = {ATTRIBUTES = (Public, ); }; };
F99A8C6F169EBF7236DA9FD6A89A4CAD /* TyphoonPassThroughTypeConverter.h in Headers */ = {isa = PBXBuildFile; fileRef = AF03A2059F417E8AEC2BCF0FFB758649 /* TyphoonPassThroughTypeConverter.h */; settings = {ATTRIBUTES = (Public, ); }; };
F9FE542AFAA108FFD4F546ADBF6690F1 /* NSNullTypeConverter.m in Sources */ = {isa = PBXBuildFile; fileRef = BDDDFCB98FA96E2B25144D59DAD84EF4 /* NSNullTypeConverter.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
FA3021DED76B9B182CC9195A60EB1209 /* NSBezierPath+SDRoundedCorners.m in Sources */ = {isa = PBXBuildFile; fileRef = D68AF173A5494072218D36ADB175CC67 /* NSBezierPath+SDRoundedCorners.m */; };
FA4D59453FAEF4399232AB1CF9ABDDBD /* RACQueueScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = EC243AD4E47E5CAA863DF364A86CA635 /* RACQueueScheduler.m */; };
FA97AAE3347E765BA649A2587F6A7BBC /* TyphoonIntrospectionUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C08AB40F42970663D76869A9E5E9143 /* TyphoonIntrospectionUtils.h */; settings = {ATTRIBUTES = (Public, ); }; };
FB1D858F09AAF47F96795E1AEAC09A19 /* UIControl+RACSignalSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F1A6A9C14EDD26D5A9D9CE5506A3971 /* UIControl+RACSignalSupport.m */; };
FBCE4760C09733E7FBE20D075564D30F /* NSInvocation+TCFInstanceBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = FD27FFD79501082E67620A5F5585D2D0 /* NSInvocation+TCFInstanceBuilder.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
FC0726705C4A0C4073F97C70C7045539 /* TyphoonRuntimeArguments.h in Headers */ = {isa = PBXBuildFile; fileRef = 88024AA70385D01B3432E99546EFA4E1 /* TyphoonRuntimeArguments.h */; settings = {ATTRIBUTES = (Public, ); }; };
FC16BB1D601656C4C5F0B8B2D2D38A65 /* RACTuple.h in Headers */ = {isa = PBXBuildFile; fileRef = AB7F2ED32F5F65686BE8BE48409DB271 /* RACTuple.h */; settings = {ATTRIBUTES = (Public, ); }; };
FCDEC6A53CF5517E1AF5B331FD65F6D9 /* SDImageCacheConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = B2E5AA16D441D1E41E9953B3FD0CE552 /* SDImageCacheConfig.m */; };
FCEE5BD645E95FF55468C4AB6D17CFDA /* UIImageView+HighlightedWebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 45AF458D3D9B4CF7A071A8AA27745113 /* UIImageView+HighlightedWebCache.m */; };
FD9BA74BACBA3F59304DDF3D5BCF6119 /* RACArraySequence.h in Headers */ = {isa = PBXBuildFile; fileRef = 9754B128A1DF8F57658751C1302FF46F /* RACArraySequence.h */; settings = {ATTRIBUTES = (Public, ); }; };
FE314B3711980149B7078A3606250317 /* TyphoonComponentFactory+Storyboard.m in Sources */ = {isa = PBXBuildFile; fileRef = 4065878CE75B090253FC030941E3DC2D /* TyphoonComponentFactory+Storyboard.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; };
FEA8BA4F82CCBD1D28DCC7EF39FB4096 /* SDImageCacheDefine.m in Sources */ = {isa = PBXBuildFile; fileRef = 4290D47244C9D83C19300F3FE4CA8170 /* SDImageCacheDefine.m */; };
FF266014693FE86BF83B9FD19AFCA071 /* TyphoonAbstractInjection.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C06ABD459BD9D2EC039A4278068AC2B /* TyphoonAbstractInjection.h */; settings = {ATTRIBUTES = (Public, ); }; };
FF298EDEF3741C40A1AB8FB9EE00CD2F /* RACMulticastConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = 66D7BE61D87C0C7666DBE752021D186B /* RACMulticastConnection.h */; settings = {ATTRIBUTES = (Public, ); }; };
FF4E53497AD777F441D3CEF6355A4258 /* TyphoonDefinitionPostProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = C529B758067B01A288574A93D2868C42 /* TyphoonDefinitionPostProcessor.h */; settings = {ATTRIBUTES = (Public, ); }; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
5AE1D14F60B6E1194AEB2341B78F3262 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */;
proxyType = 1;
remoteGlobalIDString = C260B5A26D3CD54F215E5E39371483B6;
remoteInfo = OCMock;
};
74C78EC43AE66E5D1A6C66170C1C1D61 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 94CFBA7D633ECA58DF85C327B035E6A3;
remoteInfo = "SDWebImage-SDWebImage";
};
76D528C13A764727FA8A429BB8ABC642 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 6D7BC7A068E56396BBE1DECFD19DDDEB;
remoteInfo = Typhoon;
};
9B705E4CA2FCD950E81AB005C7565BE0 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 84B44807A12996D487A4A591A481D6A0;
remoteInfo = YYModel;
};
9D64C0FC477F71B3B5C8D7DC509C043A /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 438B238ACC7DF1178D1BCE1A31983146;
remoteInfo = ReactiveObjC;
};
A26244063922B6BC1F46487F12D1016C /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 55AF53E6C77A10ED4985E04D74A8878E;
remoteInfo = Masonry;
};
B1BB2C6EA80EE90466DC3C902622C7B1 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */;
proxyType = 1;
remoteGlobalIDString = F1BCD9702276377FB5B3BDB6EAF709D7;
remoteInfo = DZNEmptyDataSet;
};
C2CBEAA478071DF837FB759F6C115BF3 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 3847153A6E5EEFB86565BA840768F429;
remoteInfo = SDWebImage;
};
C98ED4A91B240BEF49E26C808D0A8F3D /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 630AEC5FE2D10DE4AF88009DC6EC819A;
remoteInfo = "Pods-iOS-Network-Stack-Dive";
};
E08BBA5114D60374EFEC60DCE34CDCFC /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 6083682834ABE0AE7BD1CBF06CADD036;
remoteInfo = CocoaAsyncSocket;
};
F948F43B3C4BC87A483963360B1C0612 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */;
proxyType = 1;
remoteGlobalIDString = CAA047C0F5E4106F3904E8497FA17F97;
remoteInfo = Reachability;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
000F69D9699B233904DC78740C74688D /* SDInternalMacros.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDInternalMacros.h; path = SDWebImage/Private/SDInternalMacros.h; sourceTree = ""; };
004DB416A9430FE442A136525FC3D93D /* TyphoonStoryboardResolver.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = TyphoonStoryboardResolver.m; path = Source/ios/Storyboard/TyphoonStoryboardResolver.m; sourceTree = ""; };
02580255476480314DC90D373EF87D94 /* UITextView+RACSignalSupport.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UITextView+RACSignalSupport.h"; path = "ReactiveObjC/UITextView+RACSignalSupport.h"; sourceTree = ""; };
026AD1F39E6F56A1374F84466603518D /* UIImage+Transform.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIImage+Transform.m"; path = "SDWebImage/Core/UIImage+Transform.m"; sourceTree = ""; };
027098AE8C5EB84C31AACA284246D555 /* RACScheduler.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RACScheduler.m; path = ReactiveObjC/RACScheduler.m; sourceTree = ""; };
02DBA7718F60FA5F5208D3C92DD2FAE1 /* TyphoonViewHelpers.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TyphoonViewHelpers.h; path = Source/ios/Storyboard/TyphoonViewHelpers.h; sourceTree = ""; };
032501D9A06A7D0265ED238BBC7D25E3 /* NSInvocation+TCFCustomImplementation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSInvocation+TCFCustomImplementation.m"; path = "Source/Factory/Internal/NSInvocation+TCFCustomImplementation.m"; sourceTree = ""; };
032C0CB079DA640D62F451CF2BB09DBF /* NSObject+RACLifting.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSObject+RACLifting.m"; path = "ReactiveObjC/NSObject+RACLifting.m"; sourceTree = ""; };
041C0D0F13831619F5479D99056A302A /* NSArray+MASShorthandAdditions.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSArray+MASShorthandAdditions.h"; path = "Masonry/NSArray+MASShorthandAdditions.h"; sourceTree = ""; };
0453653DE2FEC83ACFBA5BA70EBEF291 /* SDWebImage-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SDWebImage-umbrella.h"; sourceTree = ""; };
04CD8685467C1425C560A1E9C0AA43B7 /* TyphoonCircularDependencyTerminator.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = TyphoonCircularDependencyTerminator.m; path = Source/Factory/Internal/TyphoonCircularDependencyTerminator.m; sourceTree = ""; };
050CAE5EF6AFDE7FE32CEE7E030CDD0E /* TyphoonInjectionByComponentFactory.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = TyphoonInjectionByComponentFactory.m; path = Source/Definition/Injections/TyphoonInjectionByComponentFactory.m; sourceTree = ""; };
0571A2C7BE7E0945F4212CD552F88BE1 /* SDAnimatedImageRep.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDAnimatedImageRep.m; path = SDWebImage/Core/SDAnimatedImageRep.m; sourceTree = ""; };
05C311CA83E10EC09FC2A8CAAF090A30 /* TyphoonComponentFactory+InstanceBuilder.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "TyphoonComponentFactory+InstanceBuilder.m"; path = "Source/Factory/Internal/TyphoonComponentFactory+InstanceBuilder.m"; sourceTree = ""; };
065E52ED2D30B3B331A00D8EB564E4D8 /* TyphoonColorConversionUtils.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = TyphoonColorConversionUtils.m; path = Source/TypeConversion/Helpers/TyphoonColorConversionUtils.m; sourceTree = ""; };
06AEB99F8D2F4D764FE818A7D523C570 /* RACCompoundDisposable.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RACCompoundDisposable.m; path = ReactiveObjC/RACCompoundDisposable.m; sourceTree = ""; };
06B502FEF6AC1E2D1DECEF63D08CC04F /* TyphoonTypeConversionUtils.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TyphoonTypeConversionUtils.h; path = Source/TypeConversion/TyphoonTypeConversionUtils.h; sourceTree = ""; };
06DAA5ED399AAB042406902A676EED50 /* NSString+RACSequenceAdditions.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSString+RACSequenceAdditions.h"; path = "ReactiveObjC/NSString+RACSequenceAdditions.h"; sourceTree = ""; };
06E2166895FA1466EACF8506DAA383DB /* NSNotificationCenter+OCMAdditions.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSNotificationCenter+OCMAdditions.m"; path = "Source/OCMock/NSNotificationCenter+OCMAdditions.m"; sourceTree = ""; };
073742952E39DC51EF6052E0131E74C6 /* ReactiveObjC.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = ReactiveObjC.modulemap; sourceTree = ""; };
073BF9A27C7F924FC6A5CF2E935DDDA8 /* SDImageTransformer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDImageTransformer.m; path = SDWebImage/Core/SDImageTransformer.m; sourceTree = ""; };
0845491EC6BC3A5BFF1808F961A5440F /* SDAnimatedImageView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDAnimatedImageView.h; path = SDWebImage/Core/SDAnimatedImageView.h; sourceTree = ""; };
0851ED90E0FAF2464FCE1AB36209BB60 /* SDImageLoadersManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDImageLoadersManager.h; path = SDWebImage/Core/SDImageLoadersManager.h; sourceTree = ""; };
08703C437757C9668B19065994EA7EBD /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
08856CD53ABCA9A62737A9BB60FD3496 /* TyphoonOrdered.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TyphoonOrdered.h; path = Source/Configuration/TyphoonOrdered.h; sourceTree = ""; };
08B6FA0A27CA67CF82F94F768E83FBBD /* TyphoonMethod.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TyphoonMethod.h; path = Source/Definition/Method/TyphoonMethod.h; sourceTree = ""; };
0A11CE004B91D55C90C201FD9902D9A8 /* Pods-iOS-Network-Stack-Dive.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-iOS-Network-Stack-Dive.release.xcconfig"; sourceTree = ""; };
0A69C05290E3D199301DB5557505AEEB /* NSOrderedSet+RACSequenceAdditions.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSOrderedSet+RACSequenceAdditions.h"; path = "ReactiveObjC/NSOrderedSet+RACSequenceAdditions.h"; sourceTree = ""; };
0AE0533E46155704FC6E99D900ECFF97 /* SDImageGIFCoder.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDImageGIFCoder.h; path = SDWebImage/Core/SDImageGIFCoder.h; sourceTree = ""; };
0B107CBC10206BACB98A3A6524024891 /* Pods-iOS-Network-Stack-Dive-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-iOS-Network-Stack-Dive-frameworks.sh"; sourceTree = ""; };
0B2C8AD692D98D47C5658A9FE6A6DE4F /* RACScheduler+Private.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "RACScheduler+Private.h"; path = "ReactiveObjC/RACScheduler+Private.h"; sourceTree = ""; };
0B91A0326C0D75CA3ED540C456F99FA7 /* RACTargetQueueScheduler.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RACTargetQueueScheduler.m; path = ReactiveObjC/RACTargetQueueScheduler.m; sourceTree = ""; };
0BB3D4EE11AB02C35A56E26CD538EA8D /* RACSerialDisposable.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RACSerialDisposable.m; path = ReactiveObjC/RACSerialDisposable.m; sourceTree = ""; };
0C08AB40F42970663D76869A9E5E9143 /* TyphoonIntrospectionUtils.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TyphoonIntrospectionUtils.h; path = Source/Utils/TyphoonIntrospectionUtils.h; sourceTree = ""; };
0C9B76DC6B005BE0C06B737507138A3E /* SDGraphicsImageRenderer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDGraphicsImageRenderer.m; path = SDWebImage/Core/SDGraphicsImageRenderer.m; sourceTree = ""; };
0CB61254C5C3C9FFDE2C23A595DB093C /* TyphoonCircularDependencyTerminator.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TyphoonCircularDependencyTerminator.h; path = Source/Factory/Internal/TyphoonCircularDependencyTerminator.h; sourceTree = ""; };
0CEAF4D7D00B3A496611716400E24A3C /* NSData+RACSupport.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSData+RACSupport.m"; path = "ReactiveObjC/NSData+RACSupport.m"; sourceTree = ""; };
0D5BC3205064C311F17DDCAF73FE5D91 /* OCMLocation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OCMLocation.m; path = Source/OCMock/OCMLocation.m; sourceTree = ""; };
0D9725591DAD7A11FB7BCF2AA89B402B /* TyphoonMethodSwizzler.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TyphoonMethodSwizzler.h; path = Source/Utils/Swizzle/TyphoonMethodSwizzler.h; sourceTree = ""; };
0DBB238DBD9086C4A06C98A1C750EA43 /* NSNotificationCenter+RACSupport.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSNotificationCenter+RACSupport.m"; path = "ReactiveObjC/NSNotificationCenter+RACSupport.m"; sourceTree = ""; };
0E69DD9AA22CCF06F1224BEB3E20EDD3 /* RACDynamicSequence.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RACDynamicSequence.h; path = ReactiveObjC/RACDynamicSequence.h; sourceTree = ""; };
0EA9D6ACAA4596DDBF52BD0B1662EB44 /* NSString+RACKeyPathUtilities.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSString+RACKeyPathUtilities.m"; path = "ReactiveObjC/NSString+RACKeyPathUtilities.m"; sourceTree = ""; };
0EB0DF190C36C4090D74F64BB54053A6 /* OCMFunctions.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMFunctions.h; path = Source/OCMock/OCMFunctions.h; sourceTree = ""; };
0ED44D2F4F4F552521A65726C787244A /* ViewController+MASAdditions.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "ViewController+MASAdditions.h"; path = "Masonry/ViewController+MASAdditions.h"; sourceTree = ""; };
0EF30728B0B55C95AD32A7BC318AC70A /* SDAnimatedImage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SDAnimatedImage.h; path = SDWebImage/Core/SDAnimatedImage.h; sourceTree = ""; };
0F07E5AA6AC318EED47D4865477C9372 /* NSButton+WebCache.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSButton+WebCache.m"; path = "SDWebImage/Core/NSButton+WebCache.m"; sourceTree = ""; };
0F3AD4FDCF5DACA1727D2EDD1CDAA03F /* TyphoonInjections.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TyphoonInjections.h; path = Source/Definition/Injections/TyphoonInjections.h; sourceTree = ""; };
0F47471F1705425CC58FB3536B619557 /* SDWebImageOperation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDWebImageOperation.m; path = SDWebImage/Core/SDWebImageOperation.m; sourceTree = ""; };
0FC01820ACD24F5CF0969F06380ABFE9 /* OCMObserverRecorder.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OCMObserverRecorder.h; path = Source/OCMock/OCMObserverRecorder.h; sourceTree = ""; };
0FFBFA01D44CE27E222D10DF44B1FBBB /* TyphoonInjectionByComponentFactory.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TyphoonInjectionByComponentFactory.h; path = Source/Definition/Injections/TyphoonInjectionByComponentFactory.h; sourceTree = ""; };
10396C86F2BA403403FECC4E3914CF31 /* GCDAsyncUdpSocket.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GCDAsyncUdpSocket.h; path = Source/GCD/GCDAsyncUdpSocket.h; sourceTree = ""; };
1051F60A0834845519249BBEEB7669EF /* Pods-iOS-Network-Stack-DiveTests-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-iOS-Network-Stack-DiveTests-umbrella.h"; sourceTree = ""; };
1061DB1631D08DF649DC362D1104033F /* TyphoonInstancePostProcessor.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TyphoonInstancePostProcessor.h; path = Source/Configuration/TyphoonInstancePostProcessor.h; sourceTree = ""; };
1083669FB5354500A46F16186007AE49 /* UIImage+ForceDecode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIImage+ForceDecode.m"; path = "SDWebImage/Core/UIImage+ForceDecode.m"; sourceTree = ""; };
10AF2F5387AC99FDF1EE38CC6AF81A5A /* NSValue+TCFUnwrapValues.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSValue+TCFUnwrapValues.h"; path = "Source/Factory/Internal/NSValue+TCFUnwrapValues.h"; sourceTree = ""; };
115C7117F68E39901C8DF436A5193C71 /* RACTestScheduler.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RACTestScheduler.m; path = ReactiveObjC/RACTestScheduler.m; sourceTree = ""; };
11CA0DD35189B363533FA7DEBEAFC7E5 /* NSSet+RACSequenceAdditions.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSSet+RACSequenceAdditions.m"; path = "ReactiveObjC/NSSet+RACSequenceAdditions.m"; sourceTree = ""; };
12676821542EF4B94B6913AF583A7A69 /* TyphoonJsonStyleConfiguration.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = TyphoonJsonStyleConfiguration.m; path = Source/Configuration/ConfigPostProcessor/TyphoonConfiguration/TyphoonJsonStyleConfiguration.m; sourceTree = ""; };
1291349B3C35F79CB6F631FD1CDCC1B0 /* RACSubscriber.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = RACSubscriber.m; path = ReactiveObjC/RACSubscriber.m; sourceTree = ""; };
12EC94936DE4F7A5029D489A3E4FB32E /* NSDictionary+CustomInjection.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSDictionary+CustomInjection.h"; path = "Source/Definition/Internal/NSDictionary+CustomInjection.h"; sourceTree = ""; };
1343632E0E45C40E0DBA77566514A62A /* SDImageCachesManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SDImageCachesManager.m; path = SDWebImage/Core/SDImageCachesManager.m; sourceTree = ""; };
13B5BE064B370CA187BEE420A188E41F /* CocoaAsyncSocket.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = CocoaAsyncSocket.modulemap; sourceTree = ""; };
13BDADC3DD9D34511EDA118822EA790E /* TyphoonParameterInjection.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TyphoonParameterInjection.h; path = Source/Definition/Injections/TyphoonParameterInjection.h; sourceTree = ""; };
13D762E3256505E91BAEA28CEAA5F68F /* TyphoonNSNumberTypeConverter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = TyphoonNSNumberTypeConverter.h; path = Source/TypeConversion/Converters/TyphoonNSNumberTypeConverter.h; sourceTree = ""; };
141EB3C3A6A1510AA72DD2C9ACD6894C /* RACReplaySubject.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = RACReplaySubject.h; path = ReactiveObjC/RACReplaySubject.h; sourceTree = "