Showing preview only (5,466K chars total). Download the full file or copy to clipboard to get everything.
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.<br/>
_No need to muck around with sockets or streams. This class handles everything for you._
- Full delegate support<br/>
_Errors, connections, read completions, write completions, progress, and disconnections all result in a call to your delegate method._
- Queued non-blocking reads and writes, with optional timeouts.<br/>
_You tell it what to read or write, and it handles everything for you. Queueing, buffering, and searching for termination sequences within the stream - all handled for you automatically._
- Automatic socket acceptance.<br/>
_Spin up a server socket, tell it to accept connections, and it will call you with new instances of itself for each connection._
- Support for TCP streams over IPv4 and IPv6.<br/>
_Automatically connect to IPv4 or IPv6 hosts. Automatically accept incoming connections over both IPv4 and IPv6 with a single instance of this class. No more worrying about multiple sockets._
- Support for TLS / SSL<br/>
_Secure your socket with ease using just a single method call. Available for both client and server sockets._
- Fully GCD based and Thread-Safe<br/>
_It runs entirely within its own GCD dispatch_queue, and is completely thread-safe. Further, the delegate methods are all invoked asynchronously onto a dispatch_queue of your choosing. This means parallel operation of your socket code, and your delegate/processing code._
## 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.<br/>
_No need to muck around with low-level sockets. This class handles everything for you._
- Full delegate support.<br/>
_Errors, send completions, receive completions, and disconnections all result in a call to your delegate method._
- Queued non-blocking send and receive operations, with optional timeouts.<br/>
_You tell it what to send or receive, and it handles everything for you. Queueing, buffering, waiting and checking errno - all handled for you automatically._
- Support for IPv4 and IPv6.<br/>
_Automatically send/recv using IPv4 and/or IPv6. No more worrying about multiple sockets._
- Fully GCD based and Thread-Safe<br/>
_It runs entirely within its own GCD dispatch_queue, and is completely thread-safe. Further, the delegate methods are all invoked asynchronously onto a dispatch_queue of your choosing. This means parallel operation of your socket code, and your delegate/processing code._
***
For those new(ish) to networking, it's recommended you **[read the wiki](https://github.com/robbiehanson/CocoaAsyncSocket/wiki)**.<br/>_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 <Foundation/Foundation.h>
#import <Security/Security.h>
#import <Security/SecureTransport.h>
#import <dispatch/dispatch.h>
#import <Availability.h>
#include <sys/socket.h> // 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<GCDAsyncSocketDelegate>)aDelegate delegateQueue:(nullable dispatch_queue_t)dq;
- (instancetype)initWithDelegate:(nullable id<GCDAsyncSocketDelegate>)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<GCDAsyncSocketDelegate>)aDelegate delegateQueue:(nullable dispatch_queue_t)dq error:(NSError**)error;
+ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nullable id<GCDAsyncSocketDelegate>)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<GCDAsyncSocketDelegate> 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<GCDAsyncSocketDelegate> __nullable * __nullable)delegatePtr delegateQueue:(dispatch_queue_t __nullable * __nullable)delegateQueuePtr;
- (void)setDelegate:(nullable id<GCDAsyncSocketDelegate>)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<GCDAsyncSocketDelegate>)delegate;
- (void)synchronouslySetDelegateQueue:(nullable dispatch_queue_t)delegateQueue;
- (void)synchronouslySetDelegate:(nullable id<GCDAsyncSocketDelegate>)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 <NSString*,NSObject*>*)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 <NSObject>
@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 <CFNetwork/CFNetwork.h>
#endif
#import <TargetConditionals.h>
#import <arpa/inet.h>
#import <fcntl.h>
#import <ifaddrs.h>
#import <netdb.h>
#import <netinet/in.h>
#import <net/if.h>
#import <sys/socket.h>
#import <sys/types.h>
#import <sys/ioctl.h>
#import <sys/poll.h>
#import <sys/uio.h>
#import <sys/un.h>
#import <unistd.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
#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 <NSString*,NSObject*>*)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 <NSString*,NSObject*>*)settings
{
if((self = [super init]))
{
tlsSettings = [settings copy];
}
return self;
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation GCDAsyncSocket
{
uint32_t flags;
uint16_t config;
__weak id<GCDAsyncSocketDelegate> 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<GCDAsyncSocketDelegate>)aDelegate delegateQueue:(dispatch_queue_t)dq
{
return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL];
}
- (instancetype)initWithDelegate:(id<GCDAsyncSocketDelegate>)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<GCDAsyncSocketDelegate>)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<GCDAsyncSocketDelegate>)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<GCDAsyncSocketDelegate>)newDelegate
{
[self setDelegate:newDelegate synchronously:NO];
}
- (void)synchronouslySetDelegate:(id<GCDAsyncSocketDelegate>)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<GCDAsyncSocketDelegate> *)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<GCDAsyncSocketDelegate>)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue
{
[self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:NO];
}
- (void)synchronouslySetDelegate:(id<GCDAsyncSocketDelegate>)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<GCDAsyncSocketDelegate> 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<GCDAsyncSocketDelegate> 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<GCDAsyncSocketDelegate> 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_
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
SYMBOL INDEX (95 symbols across 62 files)
FILE: Pods/Masonry/Masonry/MASUtilities.h
type UILayoutPriority (line 20) | typedef UILayoutPriority MASLayoutPriority;
type NSLayoutPriority (line 33) | typedef NSLayoutPriority MASLayoutPriority;
function id (line 76) | static inline id _MASBoxValue(const char *type, ...) {
FILE: Pods/Masonry/Masonry/NSArray+MASAdditions.h
type MASAxisTypeHorizontal (line 13) | typedef NS_ENUM(NSUInteger, MASAxisType) {
FILE: Pods/Masonry/Masonry/View+MASShorthandAdditions.h
function implementation (line 72) | implementation MAS_VIEW (MASShorthandAdditions)
function block (line 119) | void(NS_NOESCAPE ^)(MASConstraintMaker *))block {
function block (line 123) | void(NS_NOESCAPE ^)(MASConstraintMaker *))block {
function block (line 127) | void(NS_NOESCAPE ^)(MASConstraintMaker *))block {
FILE: Pods/OCMock/Source/OCMock/OCClassMockObject.h
function interface (line 19) | interface OCClassMockObject : OCMockObject
FILE: Pods/OCMock/Source/OCMock/OCMBlockArgCaller.h
function interface (line 19) | interface OCMBlockArgCaller : OCMArgAction<NSCopying>
FILE: Pods/OCMock/Source/OCMock/OCMBlockCaller.h
function interface (line 19) | interface OCMBlockCaller : NSObject
FILE: Pods/OCMock/Source/OCMock/OCMBoxedReturnValueProvider.h
function interface (line 19) | interface OCMBoxedReturnValueProvider : OCMObjectReturnValueProvider
FILE: Pods/OCMock/Source/OCMock/OCMExceptionReturnValueProvider.h
function interface (line 21) | interface OCMExceptionReturnValueProvider : OCMObjectReturnValueProvider
FILE: Pods/OCMock/Source/OCMock/OCMFunctionsPrivate.h
type OCMBlockDef (line 56) | struct OCMBlockDef
FILE: Pods/OCMock/Source/OCMock/OCMIndirectReturnValueProvider.h
function interface (line 19) | interface OCMIndirectReturnValueProvider : NSObject
FILE: Pods/OCMock/Source/OCMock/OCMInvocationExpectation.h
function interface (line 19) | interface OCMInvocationExpectation : OCMInvocationStub
FILE: Pods/OCMock/Source/OCMock/OCMInvocationMatcher.h
function interface (line 19) | interface OCMInvocationMatcher : NSObject
FILE: Pods/OCMock/Source/OCMock/OCMInvocationStub.h
function interface (line 19) | interface OCMInvocationStub : OCMInvocationMatcher
FILE: Pods/OCMock/Source/OCMock/OCMLocation.h
function interface (line 20) | interface OCMLocation : NSObject
FILE: Pods/OCMock/Source/OCMock/OCMMacroState.h
function interface (line 26) | interface OCMMacroState : NSObject
FILE: Pods/OCMock/Source/OCMock/OCMNonRetainingObjectReturnValueProvider.h
function interface (line 19) | interface OCMNonRetainingObjectReturnValueProvider : NSObject
FILE: Pods/OCMock/Source/OCMock/OCMNotificationPoster.h
function interface (line 19) | interface OCMNotificationPoster : NSObject
FILE: Pods/OCMock/Source/OCMock/OCMObserverRecorder.h
function interface (line 19) | interface OCMObserverRecorder : NSObject
FILE: Pods/OCMock/Source/OCMock/OCMPassByRefSetter.h
function interface (line 19) | interface OCMPassByRefSetter : OCMArgAction
FILE: Pods/OCMock/Source/OCMock/OCMQuantifier.h
function interface (line 19) | interface OCMQuantifier : NSObject
FILE: Pods/OCMock/Source/OCMock/OCMRealObjectForwarder.h
function interface (line 19) | interface OCMRealObjectForwarder : NSObject
FILE: Pods/OCMock/Source/OCMock/OCMRecorder.h
function interface (line 23) | interface OCMRecorder : NSProxy
FILE: Pods/OCMock/Source/OCMock/OCMockObject.h
function interface (line 27) | interface OCMockObject : NSProxy
FILE: Pods/OCMock/Source/OCMock/OCPartialMockObject.h
function interface (line 19) | interface OCPartialMockObject : OCClassMockObject
FILE: Pods/OCMock/Source/OCMock/OCProtocolMockObject.h
function interface (line 19) | interface OCProtocolMockObject : OCMockObject
FILE: Pods/ReactiveObjC/ReactiveObjC/RACStream.h
type ValueType (line 27) | typedef ValueType _Nonnull (^RACGenericReduceBlock)();
type RACStream (line 43) | typedef RACStream * _Nullable (^RACStreamBindBlock)(ValueType _Nullable ...
FILE: Pods/ReactiveObjC/ReactiveObjC/RACTuple.h
function end (line 104) | end
FILE: Pods/ReactiveObjC/ReactiveObjC/extobjc/RACEXTRuntimeExtensions.h
type rac_propertyMemoryManagementPolicy (line 15) | typedef enum {
type rac_propertyAttributes (line 35) | typedef struct {
FILE: Pods/ReactiveObjC/ReactiveObjC/extobjc/RACEXTScope.h
function rac_executeCleanupBlock (line 93) | static inline void rac_executeCleanupBlock (__strong rac_cleanupBlock_t ...
FILE: Pods/SDWebImage/SDWebImage/Core/NSData+ImageContentType.h
type NSInteger (line 17) | typedef NSInteger SDImageFormat
FILE: Pods/SDWebImage/SDWebImage/Core/SDImageCacheConfig.h
type SDImageCacheConfigExpireTypeAccessDate (line 13) | typedef NS_ENUM(NSUInteger, SDImageCacheConfigExpireType) {
FILE: Pods/SDWebImage/SDWebImage/Core/SDImageCacheDefine.h
type SDImageCacheTypeNone (line 16) | typedef NS_ENUM(NSInteger, SDImageCacheType) {
type NSString (line 42) | typedef NSString * _Nullable (^SDImageCacheAdditionalCachePathBlock)(NSS...
FILE: Pods/SDWebImage/SDWebImage/Core/SDImageCachesManager.h
type SDImageCachesManagerOperationPolicySerial (line 13) | typedef NS_ENUM(NSUInteger, SDImageCachesManagerOperationPolicy) {
FILE: Pods/SDWebImage/SDWebImage/Core/SDImageCoder.h
type NSString (line 15) | typedef NSString * SDImageCoderOption
type NSDictionary (line 16) | typedef NSDictionary<SDImageCoderOption, id> SDImageCoderOptions;
type NSMutableDictionary (line 17) | typedef NSMutableDictionary<SDImageCoderOption, id> SDImageCoderMutableO...
FILE: Pods/SDWebImage/SDWebImage/Core/SDImageCoderHelper.h
type SDImageCoderDecodeSolutionAutomatic (line 14) | typedef NS_ENUM(NSUInteger, SDImageCoderDecodeSolution) {
type SDImageForceDecodePolicyAutomatic (line 27) | typedef NS_ENUM(NSUInteger, SDImageForceDecodePolicy) {
function SDByteAlign (line 51) | static inline size_t SDByteAlign(size_t size, size_t alignment) {
type SDImagePixelFormat (line 56) | typedef struct SDImagePixelFormat {
FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageCacheKeyFilter.h
type NSString (line 12) | typedef NSString * _Nullable(^SDWebImageCacheKeyFilterBlock)(NSURL * _No...
FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageCacheSerializer.h
type NSData (line 12) | typedef NSData * _Nullable(^SDWebImageCacheSerializerBlock)(UIImage * _N...
FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageDefine.h
type NSString (line 13) | typedef NSString * SDWebImageContextOption
type NSDictionary (line 14) | typedef NSDictionary<SDWebImageContextOption, id> SDWebImageContext;
type NSMutableDictionary (line 15) | typedef NSMutableDictionary<SDWebImageContextOption, id> SDWebImageMutab...
FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageDownloader.h
type SDImageLoaderProgressBlock (line 109) | typedef SDImageLoaderProgressBlock SDWebImageDownloaderProgressBlock;
type SDImageLoaderCompletedBlock (line 110) | typedef SDImageLoaderCompletedBlock SDWebImageDownloaderCompletedBlock;
FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageDownloaderConfig.h
type SDWebImageDownloaderFIFOExecutionOrder (line 13) | typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageDownloaderDecryptor.h
type NSData (line 12) | typedef NSData * _Nullable (^SDWebImageDownloaderDecryptorBlock)(NSData ...
FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageDownloaderRequestModifier.h
type NSURLRequest (line 12) | typedef NSURLRequest * _Nullable (^SDWebImageDownloaderRequestModifierBl...
FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageDownloaderResponseModifier.h
type NSURLResponse (line 12) | typedef NSURLResponse * _Nullable (^SDWebImageDownloaderResponseModifier...
FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageOptionsProcessor.h
type SDWebImageOptionsResult (line 15) | typedef SDWebImageOptionsResult * _Nullable(^SDWebImageOptionsProcessorB...
FILE: Pods/SDWebImage/SDWebImage/Core/SDWebImageTransition.h
type UIViewAnimationOptions (line 15) | typedef UIViewAnimationOptions SDWebImageAnimationOptions;
FILE: Pods/SDWebImage/SDWebImage/Core/UIImage+Transform.h
type UIRectCorner (line 22) | typedef UIRectCorner SDRectCorner;
FILE: Pods/Typhoon/Source/Configuration/TyphoonAbstractDetachableComponentFactoryPostProcessor.h
function interface (line 17) | interface TyphoonAbstractDetachableComponentFactoryPostProcessor : NSObj...
FILE: Pods/Typhoon/Source/Definition/Injections/TyphoonAbstractInjection.h
type TyphoonInjectionTypeUndefined (line 17) | typedef NS_ENUM(NSInteger, TyphoonInjectionType) {
FILE: Pods/Typhoon/Source/Definition/Internal/TyphoonBlockDefinitionController.h
type TyphoonBlockDefinitionRouteInvalid (line 18) | typedef NS_ENUM(NSInteger, TyphoonBlockDefinitionRoute) {
FILE: Pods/Typhoon/Source/Definition/Method/TyphoonMethod.h
function interface (line 35) | interface TyphoonMethod : NSObject <NSCopying>
FILE: Pods/Typhoon/Source/Definition/TyphoonDefinition.h
type TyphoonScopeObjectGraph (line 46) | typedef NS_ENUM(NSInteger, TyphoonScope)
FILE: Pods/Typhoon/Source/Test/Patcher/TyphoonPatcher.h
function interface (line 29) | interface TyphoonPatcher : TyphoonAbstractDetachableComponentFactoryPost...
FILE: Pods/Typhoon/Source/TypeConversion/Converters/TyphoonPassThroughTypeConverter.h
function interface (line 19) | interface TyphoonPassThroughTypeConverter : NSObject <TyphoonTypeConverter>
FILE: Pods/Typhoon/Source/TypeConversion/Helpers/TyphoonColorConversionUtils.h
type RGBA (line 14) | struct RGBA
type RGBA (line 24) | struct RGBA
type RGBA (line 25) | struct RGBA
FILE: Pods/Typhoon/Source/TypeConversion/TyphoonTypeDescriptor.h
type TyphoonPrimitiveType (line 15) | typedef enum
FILE: iOS-Network-Stack-Dive/ArchitectureExtensions/AOP/LoggingAspect/TJPLogAspectInterface.h
type TJPLogConfig (line 21) | typedef struct TJPLogConfig {
FILE: iOS-Network-Stack-Dive/ArchitectureExtensions/VIPER-Integration/VIPER-Architecture/Router/TJPNavigationDefines.h
type TJPNavigationTransitionStyleDefault (line 20) | typedef NS_ENUM(NSUInteger, TJPNavigationTransitionStyle) {
type TJPRouterCreationStrategyHardcode (line 37) | typedef NS_ENUM(NSInteger, TJPRouterCreationStrategy) {
FILE: iOS-Network-Stack-Dive/CoreNetworkStack/TJPIMCore/Container/TJPLightweightSessionPool.h
type TJPSessionPoolConfig (line 16) | typedef struct {
type TJPSessionPoolStats (line 24) | typedef struct {
FILE: iOS-Network-Stack-Dive/CoreNetworkStack/TJPIMCore/Utility/TJPCoreTypes.h
type TJPFeatureFlag (line 12) | typedef enum {
type TJPTLVTag (line 34) | typedef enum {
type TJPTLVTagPolicyAllowDuplicates (line 61) | typedef NS_ENUM(NSUInteger, TJPTLVTagPolicy) {
type TJPDisconnectReasonNone (line 135) | typedef NS_ENUM(NSInteger, TJPDisconnectReason) {
type TJPHeartbeatModeForeground (line 176) | typedef NS_ENUM(NSUInteger, TJPHeartbeatMode) {
type TJPCarrierTypeUnknown (line 184) | typedef NS_ENUM(NSUInteger, TJPCarrierType) {
type TJPNetworkTypeUnknown (line 193) | typedef NS_ENUM(NSUInteger, TJPNetworkType) {
type TJPNetworkHealthStatusGood (line 204) | typedef NS_ENUM(NSUInteger, TJPNetworkHealthStatus) {
type TJPAppStateActive (line 212) | typedef NS_ENUM(NSUInteger, TJPAppState) {
type TJPHeartbeatStrategyBalanced (line 220) | typedef NS_ENUM(NSUInteger, TJPHeartbeatStrategy) {
type TJPHeartbeatStateEventStarted (line 228) | typedef NS_ENUM(NSUInteger, TJPHeartbeatStateEvent) {
type TJPFinalAdavancedHeader (line 241) | typedef struct {
type NSString (line 264) | typedef NSString * TJPConnectState
type NSString (line 265) | typedef NSString * TJPConnectEvent
FILE: iOS-Network-Stack-Dive/CoreNetworkStack/V2_Concurrency/TJPNetworkProtocol.h
type TJPAdavancedHeader (line 23) | typedef struct {
FILE: iOS-Network-Stack-Dive/Tools/MJRefresh/Base/MJRefreshComponent.h
function interface (line 39) | interface MJRefreshComponent : UIView
FILE: iOS-Network-Stack-Dive/Tools/TJPCache/TJPCacheManager.h
type TJPCacheUpdateReasonExpired (line 20) | typedef NS_ENUM(NSUInteger, TJPCacheUpdateReason) {
Condensed preview — 1153 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (5,515K chars).
[
{
"path": "LICENSE",
"chars": 1076,
"preview": "MIT License\n\nCopyright (c) 2025 [唐佳鹏 or AarongTang]\n\nPermission is hereby granted, free of charge, to any person obtaini"
},
{
"path": "Podfile",
"chars": 703,
"preview": "# Uncomment the next line to define a global platform for your project\n# platform :ios, '9.0'\n\ntarget 'iOS-Network-Stack"
},
{
"path": "Pods/CocoaAsyncSocket/LICENSE.txt",
"chars": 1948,
"preview": "This library is in the public domain.\nHowever, not all organizations are allowed to use such a license.\nFor example, Ger"
},
{
"path": "Pods/CocoaAsyncSocket/README.markdown",
"chars": 5712,
"preview": "# CocoaAsyncSocket\n[](https://trav"
},
{
"path": "Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncSocket.h",
"chars": 60471,
"preview": "// \n// GCDAsyncSocket.h\n// \n// This class is in the public domain.\n// Originally created by Robbie Hanson in Q3 201"
},
{
"path": "Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncSocket.m",
"chars": 234288,
"preview": "//\n// GCDAsyncSocket.m\n// \n// This class is in the public domain.\n// Originally created by Robbie Hanson in Q4 2010."
},
{
"path": "Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncUdpSocket.h",
"chars": 48117,
"preview": "// \n// GCDAsyncUdpSocket\n// \n// This class is in the public domain.\n// Originally created by Robbie Hanson of Deust"
},
{
"path": "Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncUdpSocket.m",
"chars": 141667,
"preview": "// \n// GCDAsyncUdpSocket\n// \n// This class is in the public domain.\n// Originally created by Robbie Hanson of Deust"
},
{
"path": "Pods/DZNEmptyDataSet/LICENSE",
"chars": 1107,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2016 Ignacio Romero Zurbuchen iromero@dzen.cl\n\nPermission is hereby granted, free o"
},
{
"path": "Pods/DZNEmptyDataSet/README.md",
"chars": 11696,
"preview": "DZNEmptyDataSet\n=================\n\n[](http://cocoad"
},
{
"path": "Pods/DZNEmptyDataSet/Source/UIScrollView+EmptyDataSet.h",
"chars": 10793,
"preview": "//\n// UIScrollView+EmptyDataSet.h\n// DZNEmptyDataSet\n// https://github.com/dzenbot/DZNEmptyDataSet\n//\n// Created by "
},
{
"path": "Pods/DZNEmptyDataSet/Source/UIScrollView+EmptyDataSet.m",
"chars": 37959,
"preview": "//\n// UIScrollView+EmptyDataSet.m\n// DZNEmptyDataSet\n// https://github.com/dzenbot/DZNEmptyDataSet\n//\n// Created by "
},
{
"path": "Pods/Masonry/LICENSE",
"chars": 1089,
"preview": "Copyright (c) 2011-2012 Masonry Team - https://github.com/Masonry\n\nPermission is hereby granted, free of charge, to any "
},
{
"path": "Pods/Masonry/Masonry/MASCompositeConstraint.h",
"chars": 494,
"preview": "//\n// MASCompositeConstraint.h\n// Masonry\n//\n// Created by Jonas Budelmann on 21/07/13.\n// Copyright (c) 2013 cloudl"
},
{
"path": "Pods/Masonry/Masonry/MASCompositeConstraint.m",
"chars": 4870,
"preview": "//\n// MASCompositeConstraint.m\n// Masonry\n//\n// Created by Jonas Budelmann on 21/07/13.\n// Copyright (c) 2013 cloudl"
},
{
"path": "Pods/Masonry/Masonry/MASConstraint+Private.h",
"chars": 1785,
"preview": "//\n// MASConstraint+Private.h\n// Masonry\n//\n// Created by Nick Tymchenko on 29/04/14.\n// Copyright (c) 2014 cloudlin"
},
{
"path": "Pods/Masonry/Masonry/MASConstraint.h",
"chars": 7872,
"preview": "//\n// MASConstraint.h\n// Masonry\n//\n// Created by Jonas Budelmann on 22/07/13.\n// Copyright (c) 2013 cloudling. All "
},
{
"path": "Pods/Masonry/Masonry/MASConstraint.m",
"chars": 8233,
"preview": "//\n// MASConstraint.m\n// Masonry\n//\n// Created by Nick Tymchenko on 1/20/14.\n//\n\n#import \"MASConstraint.h\"\n#import \"M"
},
{
"path": "Pods/Masonry/Masonry/MASConstraintMaker.h",
"chars": 5734,
"preview": "//\n// MASConstraintMaker.h\n// Masonry\n//\n// Created by Jonas Budelmann on 20/07/13.\n// Copyright (c) 2013 cloudling."
},
{
"path": "Pods/Masonry/Masonry/MASConstraintMaker.m",
"chars": 10450,
"preview": "//\n// MASConstraintMaker.m\n// Masonry\n//\n// Created by Jonas Budelmann on 20/07/13.\n// Copyright (c) 2013 cloudling."
},
{
"path": "Pods/Masonry/Masonry/MASLayoutConstraint.h",
"chars": 505,
"preview": "//\n// MASLayoutConstraint.h\n// Masonry\n//\n// Created by Jonas Budelmann on 3/08/13.\n// Copyright (c) 2013 Jonas Bude"
},
{
"path": "Pods/Masonry/Masonry/MASLayoutConstraint.m",
"chars": 227,
"preview": "//\n// MASLayoutConstraint.m\n// Masonry\n//\n// Created by Jonas Budelmann on 3/08/13.\n// Copyright (c) 2013 Jonas Bude"
},
{
"path": "Pods/Masonry/Masonry/MASUtilities.h",
"chars": 6273,
"preview": "//\n// MASUtilities.h\n// Masonry\n//\n// Created by Jonas Budelmann on 19/08/13.\n// Copyright (c) 2013 Jonas Budelmann."
},
{
"path": "Pods/Masonry/Masonry/MASViewAttribute.h",
"chars": 1221,
"preview": "//\n// MASViewAttribute.h\n// Masonry\n//\n// Created by Jonas Budelmann on 21/07/13.\n// Copyright (c) 2013 cloudling. A"
},
{
"path": "Pods/Masonry/Masonry/MASViewAttribute.m",
"chars": 1195,
"preview": "//\n// MASViewAttribute.m\n// Masonry\n//\n// Created by Jonas Budelmann on 21/07/13.\n// Copyright (c) 2013 cloudling. A"
},
{
"path": "Pods/Masonry/Masonry/MASViewConstraint.h",
"chars": 1279,
"preview": "//\n// MASViewConstraint.h\n// Masonry\n//\n// Created by Jonas Budelmann on 20/07/13.\n// Copyright (c) 2013 cloudling. "
},
{
"path": "Pods/Masonry/Masonry/MASViewConstraint.m",
"chars": 13358,
"preview": "//\n// MASViewConstraint.m\n// Masonry\n//\n// Created by Jonas Budelmann on 20/07/13.\n// Copyright (c) 2013 cloudling. "
},
{
"path": "Pods/Masonry/Masonry/Masonry.h",
"chars": 802,
"preview": "//\n// Masonry.h\n// Masonry\n//\n// Created by Jonas Budelmann on 20/07/13.\n// Copyright (c) 2013 cloudling. All rights"
},
{
"path": "Pods/Masonry/Masonry/NSArray+MASAdditions.h",
"chars": 2789,
"preview": "//\n// NSArray+MASAdditions.h\n//\n//\n// Created by Daniel Hammond on 11/26/13.\n//\n//\n\n#import \"MASUtilities.h\"\n#import \""
},
{
"path": "Pods/Masonry/Masonry/NSArray+MASAdditions.m",
"chars": 6154,
"preview": "//\n// NSArray+MASAdditions.m\n// \n//\n// Created by Daniel Hammond on 11/26/13.\n//\n//\n\n#import \"NSArray+MASAdditions.h\""
},
{
"path": "Pods/Masonry/Masonry/NSArray+MASShorthandAdditions.h",
"chars": 1016,
"preview": "//\n// NSArray+MASShorthandAdditions.h\n// Masonry\n//\n// Created by Jonas Budelmann on 22/07/13.\n// Copyright (c) 2013"
},
{
"path": "Pods/Masonry/Masonry/NSLayoutConstraint+MASDebugAdditions.h",
"chars": 326,
"preview": "//\n// NSLayoutConstraint+MASDebugAdditions.h\n// Masonry\n//\n// Created by Jonas Budelmann on 3/08/13.\n// Copyright (c"
},
{
"path": "Pods/Masonry/Masonry/NSLayoutConstraint+MASDebugAdditions.m",
"chars": 5771,
"preview": "//\n// NSLayoutConstraint+MASDebugAdditions.m\n// Masonry\n//\n// Created by Jonas Budelmann on 3/08/13.\n// Copyright (c"
},
{
"path": "Pods/Masonry/Masonry/View+MASAdditions.h",
"chars": 4894,
"preview": "//\n// UIView+MASAdditions.h\n// Masonry\n//\n// Created by Jonas Budelmann on 20/07/13.\n// Copyright (c) 2013 cloudling"
},
{
"path": "Pods/Masonry/Masonry/View+MASAdditions.m",
"chars": 6573,
"preview": "//\n// UIView+MASAdditions.m\n// Masonry\n//\n// Created by Jonas Budelmann on 20/07/13.\n// Copyright (c) 2013 cloudling"
},
{
"path": "Pods/Masonry/Masonry/View+MASShorthandAdditions.h",
"chars": 4772,
"preview": "//\n// UIView+MASShorthandAdditions.h\n// Masonry\n//\n// Created by Jonas Budelmann on 22/07/13.\n// Copyright (c) 2013 "
},
{
"path": "Pods/Masonry/Masonry/ViewController+MASAdditions.h",
"chars": 891,
"preview": "//\n// UIViewController+MASAdditions.h\n// Masonry\n//\n// Created by Craig Siemens on 2015-06-23.\n//\n//\n\n#import \"MASUti"
},
{
"path": "Pods/Masonry/Masonry/ViewController+MASAdditions.m",
"chars": 1302,
"preview": "//\n// UIViewController+MASAdditions.m\n// Masonry\n//\n// Created by Craig Siemens on 2015-06-23.\n//\n//\n\n#import \"ViewCo"
},
{
"path": "Pods/Masonry/README.md",
"chars": 16167,
"preview": "# Masonry [](https://travis-ci.org/SnapKit/Mason"
},
{
"path": "Pods/OCMock/License.txt",
"chars": 10174,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": "Pods/OCMock/README.md",
"chars": 276,
"preview": "OCMock\n======\n\n 2006-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/NSInvocation+OCMAdditions.m",
"chars": 19566,
"preview": "/*\n * Copyright (c) 2006-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/NSMethodSignature+OCMAdditions.h",
"chars": 983,
"preview": "/*\n * Copyright (c) 2009-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/NSMethodSignature+OCMAdditions.m",
"chars": 6698,
"preview": "/*\n * Copyright (c) 2009-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/NSNotificationCenter+OCMAdditions.h",
"chars": 892,
"preview": "/*\n * Copyright (c) 2009-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/NSNotificationCenter+OCMAdditions.m",
"chars": 1224,
"preview": "/*\n * Copyright (c) 2009-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/NSObject+OCMAdditions.h",
"chars": 868,
"preview": "/*\n * Copyright (c) 2013-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/NSObject+OCMAdditions.m",
"chars": 2232,
"preview": "/*\n * Copyright (c) 2009-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/NSValue+OCMAdditions.h",
"chars": 784,
"preview": "/*\n * Copyright (c) 2014-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/NSValue+OCMAdditions.m",
"chars": 3178,
"preview": "/*\n * Copyright (c) 2014-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCClassMockObject.h",
"chars": 982,
"preview": "/*\n * Copyright (c) 2005-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCClassMockObject.m",
"chars": 9557,
"preview": "/*\n * Copyright (c) 2005-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMArg.h",
"chars": 1495,
"preview": "/*\n * Copyright (c) 2009-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMArg.m",
"chars": 3298,
"preview": "/*\n * Copyright (c) 2009-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMArgAction.h",
"chars": 753,
"preview": "/*\n * Copyright (c) 2015-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMArgAction.m",
"chars": 744,
"preview": "/*\n * Copyright (c) 2015-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMBlockArgCaller.h",
"chars": 815,
"preview": "/*\n * Copyright (c) 2015-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMBlockArgCaller.m",
"chars": 1248,
"preview": "/*\n * Copyright (c) 2015-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMBlockCaller.h",
"chars": 873,
"preview": "/*\n * Copyright (c) 2010-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMBlockCaller.m",
"chars": 1046,
"preview": "/*\n * Copyright (c) 2010-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMBoxedReturnValueProvider.h",
"chars": 761,
"preview": "/*\n * Copyright (c) 2009-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMBoxedReturnValueProvider.m",
"chars": 2318,
"preview": "/*\n * Copyright (c) 2009-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMConstraint.h",
"chars": 1846,
"preview": "/*\n * Copyright (c) 2007-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMConstraint.m",
"chars": 3222,
"preview": "/*\n * Copyright (c) 2007-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMExceptionReturnValueProvider.h",
"chars": 804,
"preview": "/*\n * Copyright (c) 2009-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMExceptionReturnValueProvider.m",
"chars": 999,
"preview": "/*\n * Copyright (c) 2009-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMExpectationRecorder.h",
"chars": 740,
"preview": "/*\n * Copyright (c) 2004-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMExpectationRecorder.m",
"chars": 1416,
"preview": "/*\n * Copyright (c) 2004-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMFunctions.h",
"chars": 891,
"preview": "/*\n * Copyright (c) 2014-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMFunctions.m",
"chars": 19485,
"preview": "/*\n * Copyright (c) 2014-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMFunctionsPrivate.h",
"chars": 2812,
"preview": "/*\n * Copyright (c) 2014-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMIndirectReturnValueProvider.h",
"chars": 895,
"preview": "/*\n * Copyright (c) 2009-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMIndirectReturnValueProvider.m",
"chars": 1362,
"preview": "/*\n * Copyright (c) 2009-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMInvocationExpectation.h",
"chars": 870,
"preview": "/*\n * Copyright (c) 2014-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMInvocationExpectation.m",
"chars": 1683,
"preview": "/*\n * Copyright (c) 2014-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMInvocationMatcher.h",
"chars": 1154,
"preview": "/*\n * Copyright (c) 2014-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMInvocationMatcher.m",
"chars": 4184,
"preview": "/*\n * Copyright (c) 2014-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMInvocationStub.h",
"chars": 905,
"preview": "/*\n * Copyright (c) 2014-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMInvocationStub.m",
"chars": 3774,
"preview": "/*\n * Copyright (c) 2014-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMLocation.h",
"chars": 1154,
"preview": "/*\n * Copyright (c) 2014-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMLocation.m",
"chars": 1522,
"preview": "/*\n * Copyright (c) 2014-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMMacroState.h",
"chars": 1471,
"preview": "/*\n * Copyright (c) 2014-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMMacroState.m",
"chars": 5543,
"preview": "/*\n * Copyright (c) 2014-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMNonRetainingObjectReturnValueProvider.h",
"chars": 866,
"preview": "/*\n * Copyright (c) 2019-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMNonRetainingObjectReturnValueProvider.m",
"chars": 1325,
"preview": "/*\n * Copyright (c) 2019-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMNotificationPoster.h",
"chars": 865,
"preview": "/*\n * Copyright (c) 2009-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMNotificationPoster.m",
"chars": 1080,
"preview": "/*\n * Copyright (c) 2009-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMObjectReturnValueProvider.h",
"chars": 782,
"preview": "/*\n * Copyright (c) 2009-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMObjectReturnValueProvider.m",
"chars": 941,
"preview": "/*\n * Copyright (c) 2009-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMObserverRecorder.h",
"chars": 1124,
"preview": "/*\n * Copyright (c) 2009-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMObserverRecorder.m",
"chars": 2525,
"preview": "/*\n * Copyright (c) 2009-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMPassByRefSetter.h",
"chars": 814,
"preview": "/*\n * Copyright (c) 2009-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMPassByRefSetter.m",
"chars": 2176,
"preview": "/*\n * Copyright (c) 2009-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMQuantifier.h",
"chars": 1370,
"preview": "/*\n * Copyright (c) 2016-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMQuantifier.m",
"chars": 3017,
"preview": "/*\n * Copyright (c) 2016-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMRealObjectForwarder.h",
"chars": 786,
"preview": "/*\n * Copyright (c) 2010-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMRealObjectForwarder.m",
"chars": 2505,
"preview": "/*\n * Copyright (c) 2010-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMRecorder.h",
"chars": 1441,
"preview": "/*\n * Copyright (c) 2014-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMRecorder.m",
"chars": 4075,
"preview": "/*\n * Copyright (c) 2014-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMStubRecorder.h",
"chars": 3572,
"preview": "/*\n * Copyright (c) 2004-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMStubRecorder.m",
"chars": 5482,
"preview": "/*\n * Copyright (c) 2004-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMVerifier.h",
"chars": 895,
"preview": "/*\n * Copyright (c) 2014-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMVerifier.m",
"chars": 1585,
"preview": "/*\n * Copyright (c) 2014-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMock.h",
"chars": 1041,
"preview": "/*\n * Copyright (c) 2004-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMockMacros.h",
"chars": 4908,
"preview": "/*\n * Copyright (c) 2014-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMockObject.h",
"chars": 2499,
"preview": "/*\n * Copyright (c) 2004-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCMockObject.m",
"chars": 16597,
"preview": "/*\n * Copyright (c) 2004-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCObserverMockObject.h",
"chars": 1275,
"preview": "/*\n * Copyright (c) 2009-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCObserverMockObject.m",
"chars": 4384,
"preview": "/*\n * Copyright (c) 2009-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCPartialMockObject.h",
"chars": 870,
"preview": "/*\n * Copyright (c) 2009-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCPartialMockObject.m",
"chars": 11754,
"preview": "/*\n * Copyright (c) 2009-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCProtocolMockObject.h",
"chars": 799,
"preview": "/*\n * Copyright (c) 2005-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/OCMock/Source/OCMock/OCProtocolMockObject.m",
"chars": 1972,
"preview": "/*\n * Copyright (c) 2005-2021 Erik Doernenburg and contributors\n *\n * Licensed under the Apache License, Version 2.0 ("
},
{
"path": "Pods/Pods.xcodeproj/project.pbxproj",
"chars": 556884,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 77;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "Pods/Pods.xcodeproj/xcuserdata/aarongtang.xcuserdatad/xcschemes/CocoaAsyncSocket.xcscheme",
"chars": 2041,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1600\"\n version = \"1.3\">\n <BuildAction\n "
},
{
"path": "Pods/Pods.xcodeproj/xcuserdata/aarongtang.xcuserdatad/xcschemes/DZNEmptyDataSet.xcscheme",
"chars": 2039,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1600\"\n version = \"1.3\">\n <BuildAction\n "
},
{
"path": "Pods/Pods.xcodeproj/xcuserdata/aarongtang.xcuserdatad/xcschemes/Masonry.xcscheme",
"chars": 2023,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1600\"\n version = \"1.3\">\n <BuildAction\n "
},
{
"path": "Pods/Pods.xcodeproj/xcuserdata/aarongtang.xcuserdatad/xcschemes/OCMock.xcscheme",
"chars": 2021,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1600\"\n version = \"1.3\">\n <BuildAction\n "
},
{
"path": "Pods/Pods.xcodeproj/xcuserdata/aarongtang.xcuserdatad/xcschemes/Pods-iOS-Network-Stack-Dive.xcscheme",
"chars": 2063,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1600\"\n version = \"1.3\">\n <BuildAction\n "
},
{
"path": "Pods/Pods.xcodeproj/xcuserdata/aarongtang.xcuserdatad/xcschemes/Pods-iOS-Network-Stack-DiveTests.xcscheme",
"chars": 2073,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1600\"\n version = \"1.3\">\n <BuildAction\n "
},
{
"path": "Pods/Pods.xcodeproj/xcuserdata/aarongtang.xcuserdatad/xcschemes/Reachability.xcscheme",
"chars": 2033,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1600\"\n version = \"1.3\">\n <BuildAction\n "
},
{
"path": "Pods/Pods.xcodeproj/xcuserdata/aarongtang.xcuserdatad/xcschemes/ReactiveObjC.xcscheme",
"chars": 2033,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1600\"\n version = \"1.3\">\n <BuildAction\n "
},
{
"path": "Pods/Pods.xcodeproj/xcuserdata/aarongtang.xcuserdatad/xcschemes/SDWebImage-SDWebImage.xcscheme",
"chars": 2037,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1600\"\n version = \"1.3\">\n <BuildAction\n "
},
{
"path": "Pods/Pods.xcodeproj/xcuserdata/aarongtang.xcuserdatad/xcschemes/SDWebImage.xcscheme",
"chars": 2029,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1600\"\n version = \"1.3\">\n <BuildAction\n "
},
{
"path": "Pods/Pods.xcodeproj/xcuserdata/aarongtang.xcuserdatad/xcschemes/Typhoon.xcscheme",
"chars": 2023,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1600\"\n version = \"1.3\">\n <BuildAction\n "
},
{
"path": "Pods/Pods.xcodeproj/xcuserdata/aarongtang.xcuserdatad/xcschemes/YYModel.xcscheme",
"chars": 2023,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1600\"\n version = \"1.3\">\n <BuildAction\n "
},
{
"path": "Pods/Pods.xcodeproj/xcuserdata/aarongtang.xcuserdatad/xcschemes/xcschememanagement.plist",
"chars": 1946,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Pods/Reachability/LICENCE.txt",
"chars": 1297,
"preview": "Copyright (c) 2011-2013, Tony Million.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or "
},
{
"path": "Pods/Reachability/README.md",
"chars": 5457,
"preview": "[](https://www.ve"
},
{
"path": "Pods/Reachability/Reachability.h",
"chars": 3812,
"preview": "/*\n Copyright (c) 2011, Tony Million.\n All rights reserved.\n \n Redistribution and use in source and binary forms, with o"
},
{
"path": "Pods/Reachability/Reachability.m",
"chars": 13939,
"preview": "/*\n Copyright (c) 2011, Tony Million.\n All rights reserved.\n \n Redistribution and use in source and binary forms, with o"
},
{
"path": "Pods/ReactiveObjC/LICENSE.md",
"chars": 1093,
"preview": "**Copyright (c) 2012 - 2016, GitHub, Inc.**\n**All rights reserved.**\n\nPermission is hereby granted, free of charge, to a"
},
{
"path": "Pods/ReactiveObjC/README.md",
"chars": 20218,
"preview": "# ReactiveObjC\n\n_NOTE: This is legacy introduction to the Objective-C ReactiveCocoa, which is\nnow known as ReactiveObjC."
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/MKAnnotationView+RACSignalSupport.h",
"chars": 798,
"preview": "//\n// MKAnnotationView+RACSignalSupport.h\n// ReactiveObjC\n//\n// Created by Zak Remer on 3/31/15.\n// Copyright (c) 20"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/MKAnnotationView+RACSignalSupport.m",
"chars": 835,
"preview": "//\n// MKAnnotationView+RACSignalSupport.m\n// ReactiveObjC\n//\n// Created by Zak Remer on 3/31/15.\n// Copyright (c) 20"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSArray+RACSequenceAdditions.h",
"chars": 592,
"preview": "//\n// NSArray+RACSequenceAdditions.h\n// ReactiveObjC\n//\n// Created by Justin Spahr-Summers on 2012-10-29.\n// Copyrig"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSArray+RACSequenceAdditions.m",
"chars": 384,
"preview": "//\n// NSArray+RACSequenceAdditions.m\n// ReactiveObjC\n//\n// Created by Justin Spahr-Summers on 2012-10-29.\n// Copyrig"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSData+RACSupport.h",
"chars": 625,
"preview": "//\n// NSData+RACSupport.h\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 5/11/12.\n// Copyright (c) 2012 GitHub, "
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSData+RACSupport.m",
"chars": 917,
"preview": "//\n// NSData+RACSupport.m\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 5/11/12.\n// Copyright (c) 2012 GitHub, "
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSDictionary+RACSequenceAdditions.h",
"chars": 1183,
"preview": "//\n// NSDictionary+RACSequenceAdditions.h\n// ReactiveObjC\n//\n// Created by Justin Spahr-Summers on 2012-10-29.\n// Co"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSDictionary+RACSequenceAdditions.m",
"chars": 780,
"preview": "//\n// NSDictionary+RACSequenceAdditions.m\n// ReactiveObjC\n//\n// Created by Justin Spahr-Summers on 2012-10-29.\n// Co"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSEnumerator+RACSequenceAdditions.h",
"chars": 573,
"preview": "//\n// NSEnumerator+RACSequenceAdditions.h\n// ReactiveObjC\n//\n// Created by Uri Baghin on 07/01/2013.\n// Copyright (c"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSEnumerator+RACSequenceAdditions.m",
"chars": 452,
"preview": "//\n// NSEnumerator+RACSequenceAdditions.m\n// ReactiveObjC\n//\n// Created by Uri Baghin on 07/01/2013.\n// Copyright (c"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSFileHandle+RACSupport.h",
"chars": 464,
"preview": "//\n// NSFileHandle+RACSupport.h\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 5/10/12.\n// Copyright (c) 2012 Gi"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSFileHandle+RACSupport.m",
"chars": 1100,
"preview": "//\n// NSFileHandle+RACSupport.m\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 5/10/12.\n// Copyright (c) 2012 Gi"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSIndexSet+RACSequenceAdditions.h",
"chars": 605,
"preview": "//\n// NSIndexSet+RACSequenceAdditions.h\n// ReactiveObjC\n//\n// Created by Sergey Gavrilyuk on 12/17/13.\n// Copyright "
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSIndexSet+RACSequenceAdditions.m",
"chars": 392,
"preview": "//\n// NSIndexSet+RACSequenceAdditions.m\n// ReactiveObjC\n//\n// Created by Sergey Gavrilyuk on 12/17/13.\n// Copyright "
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSInvocation+RACTypeParsing.h",
"chars": 1808,
"preview": "//\n// NSInvocation+RACTypeParsing.h\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 11/17/12.\n// Copyright (c) 20"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSInvocation+RACTypeParsing.m",
"chars": 8151,
"preview": "//\n// NSInvocation+RACTypeParsing.m\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 11/17/12.\n// Copyright (c) 20"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSNotificationCenter+RACSupport.h",
"chars": 525,
"preview": "//\n// NSNotificationCenter+RACSupport.h\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 5/10/12.\n// Copyright (c)"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSNotificationCenter+RACSupport.m",
"chars": 930,
"preview": "//\n// NSNotificationCenter+RACSupport.m\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 5/10/12.\n// Copyright (c)"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSObject+RACDeallocating.h",
"chars": 797,
"preview": "//\n// NSObject+RACDeallocating.h\n// ReactiveObjC\n//\n// Created by Kazuo Koga on 2013/03/15.\n// Copyright (c) 2013 Gi"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSObject+RACDeallocating.m",
"chars": 3255,
"preview": "//\n// NSObject+RACDeallocating.m\n// ReactiveObjC\n//\n// Created by Kazuo Koga on 2013/03/15.\n// Copyright (c) 2013 Gi"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSObject+RACDescription.h",
"chars": 509,
"preview": "//\n// NSObject+RACDescription.h\n// ReactiveObjC\n//\n// Created by Justin Spahr-Summers on 2013-05-13.\n// Copyright (c"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSObject+RACDescription.m",
"chars": 994,
"preview": "//\n// NSObject+RACDescription.m\n// ReactiveObjC\n//\n// Created by Justin Spahr-Summers on 2013-05-13.\n// Copyright (c"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSObject+RACKVOWrapper.h",
"chars": 1936,
"preview": "//\n// NSObject+RACKVOWrapper.h\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 10/11/11.\n// Copyright (c) 2011 Gi"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSObject+RACKVOWrapper.m",
"chars": 8191,
"preview": "//\n// NSObject+RACKVOWrapper.m\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 10/11/11.\n// Copyright (c) 2011 Gi"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSObject+RACLifting.h",
"chars": 1937,
"preview": "//\n// NSObject+RACLifting.h\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 10/13/12.\n// Copyright (c) 2012 GitHu"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSObject+RACLifting.m",
"chars": 2915,
"preview": "//\n// NSObject+RACLifting.m\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 10/13/12.\n// Copyright (c) 2012 GitHu"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSObject+RACPropertySubscribing.h",
"chars": 4757,
"preview": "//\n// NSObject+RACPropertySubscribing.h\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 3/2/12.\n// Copyright (c) "
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSObject+RACPropertySubscribing.m",
"chars": 2890,
"preview": "//\n// NSObject+RACPropertySubscribing.m\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 3/2/12.\n// Copyright (c) "
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSObject+RACSelectorSignal.h",
"chars": 3778,
"preview": "//\n// NSObject+RACSelectorSignal.h\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 3/18/13.\n// Copyright (c) 2013"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSObject+RACSelectorSignal.m",
"chars": 13275,
"preview": "//\n// NSObject+RACSelectorSignal.m\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 3/18/13.\n// Copyright (c) 2013"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSOrderedSet+RACSequenceAdditions.h",
"chars": 602,
"preview": "//\n// NSOrderedSet+RACSequenceAdditions.h\n// ReactiveObjC\n//\n// Created by Justin Spahr-Summers on 2012-10-29.\n// Co"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSOrderedSet+RACSequenceAdditions.m",
"chars": 441,
"preview": "//\n// NSOrderedSet+RACSequenceAdditions.m\n// ReactiveObjC\n//\n// Created by Justin Spahr-Summers on 2012-10-29.\n// Co"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSSet+RACSequenceAdditions.h",
"chars": 588,
"preview": "//\n// NSSet+RACSequenceAdditions.h\n// ReactiveObjC\n//\n// Created by Justin Spahr-Summers on 2012-10-29.\n// Copyright"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSSet+RACSequenceAdditions.m",
"chars": 417,
"preview": "//\n// NSSet+RACSequenceAdditions.m\n// ReactiveObjC\n//\n// Created by Justin Spahr-Summers on 2012-10-29.\n// Copyright"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSString+RACKeyPathUtilities.h",
"chars": 989,
"preview": "//\n// NSString+RACKeyPathUtilities.h\n// ReactiveObjC\n//\n// Created by Uri Baghin on 05/05/2013.\n// Copyright (c) 201"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSString+RACKeyPathUtilities.m",
"chars": 866,
"preview": "//\n// NSString+RACKeyPathUtilities.m\n// ReactiveObjC\n//\n// Created by Uri Baghin on 05/05/2013.\n// Copyright (c) 201"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSString+RACSequenceAdditions.h",
"chars": 629,
"preview": "//\n// NSString+RACSequenceAdditions.h\n// ReactiveObjC\n//\n// Created by Justin Spahr-Summers on 2012-10-29.\n// Copyri"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSString+RACSequenceAdditions.m",
"chars": 390,
"preview": "//\n// NSString+RACSequenceAdditions.m\n// ReactiveObjC\n//\n// Created by Justin Spahr-Summers on 2012-10-29.\n// Copyri"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSString+RACSupport.h",
"chars": 690,
"preview": "//\n// NSString+RACSupport.h\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 5/11/12.\n// Copyright (c) 2012 GitHub"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSString+RACSupport.m",
"chars": 984,
"preview": "//\n// NSString+RACSupport.m\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 5/11/12.\n// Copyright (c) 2012 GitHub"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSURLConnection+RACSupport.h",
"chars": 893,
"preview": "//\n// NSURLConnection+RACSupport.h\n// ReactiveObjC\n//\n// Created by Justin Spahr-Summers on 2013-10-01.\n// Copyright"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSURLConnection+RACSupport.m",
"chars": 1843,
"preview": "//\n// NSURLConnection+RACSupport.m\n// ReactiveObjC\n//\n// Created by Justin Spahr-Summers on 2013-10-01.\n// Copyright"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSUserDefaults+RACSupport.h",
"chars": 864,
"preview": "//\n// NSUserDefaults+RACSupport.h\n// ReactiveObjC\n//\n// Created by Matt Diephouse on 12/19/13.\n// Copyright (c) 2013"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/NSUserDefaults+RACSupport.m",
"chars": 1480,
"preview": "//\n// NSUserDefaults+RACSupport.m\n// ReactiveObjC\n//\n// Created by Matt Diephouse on 12/19/13.\n// Copyright (c) 2013"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/RACAnnotations.h",
"chars": 243,
"preview": "//\n// RACAnnotations.h\n// ReactiveObjC\n//\n// Created by Eric Horacek on 3/31/17.\n// Copyright © 2017 GitHub. All rig"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/RACArraySequence.h",
"chars": 515,
"preview": "//\n// RACArraySequence.h\n// ReactiveObjC\n//\n// Created by Justin Spahr-Summers on 2012-10-29.\n// Copyright (c) 2012 "
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/RACArraySequence.m",
"chars": 3138,
"preview": "//\n// RACArraySequence.m\n// ReactiveObjC\n//\n// Created by Justin Spahr-Summers on 2012-10-29.\n// Copyright (c) 2012 "
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/RACBehaviorSubject.h",
"chars": 602,
"preview": "//\n// RACBehaviorSubject.h\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 3/16/12.\n// Copyright (c) 2012 GitHub,"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/RACBehaviorSubject.m",
"chars": 1226,
"preview": "//\n// RACBehaviorSubject.m\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 3/16/12.\n// Copyright (c) 2012 GitHub,"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/RACBlockTrampoline.h",
"chars": 946,
"preview": "//\n// RACBlockTrampoline.h\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 10/21/12.\n// Copyright (c) 2012 GitHub"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/RACBlockTrampoline.m",
"chars": 5703,
"preview": "//\n// RACBlockTrampoline.m\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 10/21/12.\n// Copyright (c) 2012 GitHub"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/RACChannel.h",
"chars": 2983,
"preview": "//\n// RACChannel.h\n// ReactiveObjC\n//\n// Created by Uri Baghin on 01/01/2013.\n// Copyright (c) 2013 GitHub, Inc. All"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/RACChannel.m",
"chars": 2450,
"preview": "//\n// RACChannel.m\n// ReactiveObjC\n//\n// Created by Uri Baghin on 01/01/2013.\n// Copyright (c) 2013 GitHub, Inc. All"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/RACCommand.h",
"chars": 4870,
"preview": "//\n// RACCommand.h\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 3/3/12.\n// Copyright (c) 2012 GitHub, Inc. All"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/RACCommand.m",
"chars": 6139,
"preview": "//\n// RACCommand.m\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 3/3/12.\n// Copyright (c) 2012 GitHub, Inc. All"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/RACCompoundDisposable.h",
"chars": 1769,
"preview": "//\n// RACCompoundDisposable.h\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 11/30/12.\n// Copyright (c) 2012 Git"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/RACCompoundDisposable.m",
"chars": 6877,
"preview": "//\n// RACCompoundDisposable.m\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 11/30/12.\n// Copyright (c) 2012 Git"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/RACCompoundDisposableProvider.d",
"chars": 190,
"preview": "provider RACCompoundDisposable {\n probe added(char *compoundDisposable, char *disposable, long newTotal);\n probe r"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/RACDelegateProxy.h",
"chars": 878,
"preview": "//\n// RACDelegateProxy.h\n// ReactiveObjC\n//\n// Created by Cody Krieger on 5/19/12.\n// Copyright (c) 2012 GitHub, Inc"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/RACDelegateProxy.m",
"chars": 1883,
"preview": "//\n// RACDelegateProxy.m\n// ReactiveObjC\n//\n// Created by Cody Krieger on 5/19/12.\n// Copyright (c) 2012 GitHub, Inc"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/RACDisposable.h",
"chars": 988,
"preview": "//\n// RACDisposable.h\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 3/16/12.\n// Copyright (c) 2012 GitHub, Inc."
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/RACDisposable.m",
"chars": 1805,
"preview": "//\n// RACDisposable.m\n// ReactiveObjC\n//\n// Created by Josh Abernathy on 3/16/12.\n// Copyright (c) 2012 GitHub, Inc."
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/RACDynamicSequence.h",
"chars": 705,
"preview": "//\n// RACDynamicSequence.h\n// ReactiveObjC\n//\n// Created by Justin Spahr-Summers on 2012-10-29.\n// Copyright (c) 201"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/RACDynamicSequence.m",
"chars": 5738,
"preview": "//\n// RACDynamicSequence.m\n// ReactiveObjC\n//\n// Created by Justin Spahr-Summers on 2012-10-29.\n// Copyright (c) 201"
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/RACDynamicSignal.h",
"chars": 417,
"preview": "//\n// RACDynamicSignal.h\n// ReactiveObjC\n//\n// Created by Justin Spahr-Summers on 2013-10-10.\n// Copyright (c) 2013 "
},
{
"path": "Pods/ReactiveObjC/ReactiveObjC/RACDynamicSignal.m",
"chars": 1532,
"preview": "//\n// RACDynamicSignal.m\n// ReactiveObjC\n//\n// Created by Justin Spahr-Summers on 2013-10-10.\n// Copyright (c) 2013 "
}
]
// ... and 953 more files (download for full content)
About this extraction
This page contains the full source code of the CodeAcmen/ios-async-socket-explorer GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 1153 files (4.8 MB), approximately 1.3M tokens, and a symbol index with 95 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.