RXNetworkOperation
is a powerful tool that performs http
requests in a few lines of code.
The library represents several subclasses of NSOperation
, which contain a massive layer of logic for connecting to the Internet.
The main advantage of the technology, besides ease of use, is amazing conciseness.
Comparing with the existing solutions on the market iOS
/OS X
development using RXNO
, in some specific cases you will need to write tens of times less code than if you use traditional approaches.
RXNetworkOperation
was created to be able to makehttp requests
without having a lot of imported dependencies.
You no longer need different AFNetworking
/Alamofire
, you no longer need to become a delegate to NSURLSession
and implement the same methods in every controller.
Just import #import <RXNetworkOperation/RXNetworkOperation.h>
and make requests instantly.
You will also feel complete power over network operations, which you can stop and resume at any time.
The library provides you with several types of operations to choose from.
You will need to choose one specifically for your situation.
Class name | Abbreviation | Functional responsibility |
---|---|---|
RXNO_DataTaskOperation |
DTO | Downloads data without writing to disk. Uploads data to the server. This is a universal and most commonly used class. Widely used for most day-to-day tasks, getting json , downloading images uploading data. |
RXNO_DownloadOperation |
DO | Downloads data and writes it to disk as a separate file. |
RXNO_UploadOperation |
UO | It is intended for uploading data to the server, unlike DTO it has the distinctive ability to load data located on the device disk (without first converting it to NSData ), for this it takes a local NSURL as parameters. |
Since all operations are direct inheritors of the NSOperation class, they, like their parent, have all the advantages of this technology.
Which are in the implementation of full control over the operation process.
All possible methods will be listed below manual control.
Name of method | The result of executing |
---|---|
- (RXNO_OperationState) startSuspendResume | Changes the state of the operation every time the method is called. (ReadyToStart)->(Working)->(Suspended)-(Resume)->... |
- (void) start | Starts the operation for the first time. Later, if the operation is suspended, then it will be resumed this time (and not started again). |
- (void) suspendTask | Suspends the execution of a running operation. |
- (void) resumeTask | Resumes a previously suspended operation. |
- (void) cancel | Cancels the operation. (You cannot resume execution after calling this method.) |
- (BO*) syncStart | Starts the synchronous execution of the operation. |
- (void) retainOperationAndStart | Runs the operation asynchronously and holds the operation in memory by itself. This is required so that you do not have to create a property for the operation. |
If you want to manage operations in an automatic mode, you can simply insert them into the NSOperationQueue
, which will automatically start executing them.
The framework provides two ways to interact:
-
Working with blocks.
-
Working with delegates.
Unlike the disadvantages of NSURLSessionTask
, any operation provides equal opportunities for each of the methods.
For example, if you want to display the loading process, then using blocks you can also get the necessary information.
By default, the NSOperation
class uses several BOOL
variables to determine the status of an operation, which is an extremely inconvenient engineering solution.
In RXNO, this issue has been radically rethought, and now each operation has a property state
, which stores its status.
For example, if the operation was canceled, then its status will be equal to RXNO_FailedFinished
.
And if it was successful, then RXNO_SuccessFinished
.
To support downloading in the background, you need to create your own session with the backgroundSession
configuration, and then set the created session in the privateSession
property.
If you want your operation (with a private session) to support background loading, then you must add a block from this method to the RXNO_BaseOperation.backgroundCompletions
dictionary.
By default, all operations are performed in the session, which is property of the RXNO_BaseOperation
class.
But while developing this class, an option was provided for when the user may need to be able to initialize his own session with custom parameters.
As for example, it is shown above, when for the ability to download data in the background (this is when the iphone is locked), you need a session with a special configuration.
Especially for that case, a privateSession
property was created, if you initialize which, then the task
property of that operation will be created with the help of privateSession
, not defaultSession
.
@interface RXNO_BaseOperation : NSOperation
...
@property (nonatomic, strong, nullable) NSURLSession* privateSession;
@property (nonatomic, strong, class) NSURLSession* defaultSession;
@property (nonatomic, strong, readonly) NSURLSessionTask* task;
...
@end
When you create a private session, you MUST specify an object as a delegate
RXNO_BaseOperation.internal_delegate
.
This is necessary for the correct functioning of your operation.
Otherwise, no delegate method and no block will be called.
NSURLSession* privateSession =
[NSURLSession sessionWithConfiguration:privateConfig
delegate:RXNO_BaseOperation.internal_delegate
delegateQueue:nil];
One of the main competitive advantages of RXNO over competing technologies is the ability to synchronously execute an http-request
, which in turn completely relieves the user of callback-hell
.
To perform an operation synchronously, you just need call the syncStart
method.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
DTO* getImageOp = [[DTO GET:url] syncStart];
UIImage* image = [UIImage imageWithData:getImageOp.receivedData]
});
When working with table views, when the user is heavily loading content, there may often be a situation when you need to limit the frequency of network requests.
To solve this issue, the property timeLimitBetweenRunsOperations
was implemented, the default value of which is 0
.
The limiting mechanism functions according to the following principle:
When the start
method is called, if the property timeLimitBetweenRunsOperations
contains a value greater than zero, the property NSDate* dateDelayAfterExecution
is initialized, which by default will always contain the date 24 hours from the start of the operation.
As soon as the operation is completed, the property dateDelayAfterExecution
will be initialized (of course, if timeLimitBetweenRunsOperations
is greater than zero) to the date current moment
+ the interval from timeLimitBetweenRunsOperations
.
This will allow you to later check whether this date has expired before starting the next operation.
This practice can be useful not only for table views, but also for ordinary buttons, after pressing which a request is sent to the server.
This mechanism protects the user from accidentally sending a request several times in a row, and also reduces the load on server if the application is used by a large number of users.
Also from the listing above you can see methods like isWorkingOrInProcess
and isFinishedOrCancelled
.
These methods return BOOL
variables if the state
of the operation has certain values.
Method | Returns YES, if: |
---|---|
- (BOOL) isWorkingOrInProcess | state == RXNO_Working /RXNO_ReadyToStart /RXNO_Susspended |
- (BOOL) isFinishedOrCancelled | state == RXNO_SuccessFinished /RXNO_FailiedFinished /RXNO_Cancelled |
If after the end of the operation you want to print its `state`, then for your convenience the` stateDescription` function was created.
[[DTO GET:url completion:^(DTO * _Nonnull op, NSError * _Nullable error) {
NSLog(@"%@",[op stateDescription]);
}] retainOperationAndStart];
Operations of the RXNO framework support a full cycle of changes in their execution states, according to the principle of a single play/stop
button.
That is, if you call the startSuspendResume
method, then it transfers the operation to the following boolean state, for example (ReadyToStart) -> (Working) -> (Suspended) - (Resume) -> ...
Also, unlike directly using apple technologies or third-party libraries, you can pause execution without any additional logic, and then resume in one line.
That is, you personally do not have to re-initialize something.
But there is one important point, cancel
method), then it cannot be resumed.
The operation can be resumed only if you called the suspendTask
method before.
- (IBAction) startSuspendResumeOperation
{
[self.downoadMediaOperation startSuspendResume];
}
- (IBAction) cancelDownloadingMediaContent
{
[self.downloadMediaOperation cancel];
}
If you use NSOperationQueue
to start operations, which has a limitation on simultaneous execution of operations, you may have the following situation.
When the user uses UINavigationController
, it goes to the next screen, from which N
operations are added to the queue. And they do not have time to execute all at the same time (due to the restriction described above), and then the user leaves this controller, the following circumstance arises.
De facto, you have already left that controller, and there are operations in the queue, when their turn comes up, they will be performed, that is, they will download or unload some data.
But this will not make any sense, because at the moment it is no longer relevant.
Therefore, you need to use a certain mechanism of the RXNO
library, which allows you to timely cancel operations.
Accordingly, we are faced with the task of how, passing through the operations
array of our queue, we can determine that these operations were created on this controller?
One of the possible solutions would be the practice of "marking" that the given operation was created by this controller.
In order for the user to be able to mark, each operation has the property owner
.
It is recommended to write to it exactly the memory address of the parent object (it can be your controller or viewmodel).
For this, there is also an auxiliary method + addressInMemory:
of the RXNO_BaseOperation
class.
Further in the destructor method dealloc
of your parent object, you have two options for canceling operations..
-
cancelAllNetworkOperationsByEqual - Loops through
queue.operations
and cancels all operations that have the same object in property asowner
as the passed object from argument. The check is done by calling the isEqual function. -
cancelAllNetworkOperationsByEqualToString - Loops through
queue.operations
and cancels all operations that have the same character set inowner
as the passed object from argument. The check is done by calling the isEqualToString function.
isEqual
), then two strings that contain the same character set will be recognized as different objects, since they are based in different memory locations.
Therefore, it is highly recommended to use the second method, which checks for the identity of the character set.
Example:
Since each service where the user is given the opportunity to create a personal account, one way or another implements the mechanism for issuing tokens after successful authentication, the absolute majority of developers face the following questions:
-
"What should I do when a completed http request fails with a 401 error?"
-
"How to get a fresh token?"
And the most important question:
- "How do I retry a failed network operation?"
The first two questions can be attributed more to the area of responsibility of a certain APIManager
.
And the last one is entirely on the side of responsibility of the framework.
Especially for such cases, the RXNO_BaseOperation
class has an array of so-called "postponedOperations".
In fact, the name speaks for itself, if an operation ends with an error, then it is" postponed " token and then re-executed.
The first stage of processing this error can be seen in the diagram below:
As a rule of thumb, if we move the immediate logic of handling the 401
-th error to another method, so that it is not contained in the completion
block of each network operation.
Below are a few fairly complex pieces of code from a real application to show how such a mechanism is roughly implemented.
(note: examples are abbreviated for better understanding and focus on the main points.)
So, first we can see the API manager method, which initializes the network operation, and in its completion
block the checkOnServerAndOtherError
function is called, which directly checks (including the 401
error) and "postpones" the operation if it is need to.
checkOnServerAndOtherError
checks the operation for errors.
If it finds a 401
, it will postpone the operation by calling the postponeOperation
method.
Also, if the operation was performed synchronously, then the property isMayUnlockSemaphore
will be set to NO
, so that we first get a new token, perform the operation itself, and when it is successful, only then it will be possible to unblock the stream.
A fresh token is received in the receiveTokenFromWebViewAuth
method, or an error if it was not received.
If there was an error in obtaining the token, then we must unblock the previously frozen threads, then remove all pending operations from the array.
If the token was successfully received, then we save it and call the performPostponedOperationsOnQueue
method.
In the updateOperationBlock
block, we have to insert a new token in each operation, and then return this array back to the block.
(note: for some services the token can be passed in parameters, and for some only in headers).
Further, the method receives operations with the inserted fresh token, performs some internal tricks and inserts the operations into the queue for execution.
Chief among this example is the sequence of methods called.
-
First, postpone the operation with
postponeOperation
. -
And after receiving the token, we call
performPostponedOperationsOnQueue
to insert a new token into each pending operation and submit them for execution.
When you need to execute several http-requests
in a row to perform a certain functional duty, when the data for each next request is formed from the received response to the previous request, you inevitably come across such a phenomenon as callback-hell
.
To avoid this phenomenon, the so-called "group operations" have been developed.
RXNO_GroupOperation
has some similarities with NSBlockOperation
, and is intended so that in the body of its mainBlock
you synchronously start the execution of normal network operations, to avoid the occurrence of callback-hell
.
In the example below, we initialize a group operation, then initialize two network operations and execute them synchronously.
Thus, when using group operations, we completely get rid of such a phenomenon as callback-hell, which significantly saves development time.
Also, group operations support unlimited nesting, which means that one group operation can be inserted into the body of another, which ultimately allows the user to retrieve data by calling all of one method.
An important question when working with group operations is the following:
"How do I cancel a group operation?".
The group operation itself can be canceled simply by calling the cancel
method, but in order to cancel the operations that are performed in it, you need to write conditions in their progress
blocks.
Pay attention to the property result
and resultClass
, in which we write the result of the group operation.
This is done for general convenience, because a group operation can be stored as a property, and the result
variable will always allow you to quickly access the results of its activity.
For example, when it might come in handy:
Imagine that we need to get an array of grades for each subject, and then, based on all of them, calculate the average grade.
It will be most convenient not to create a separate property in the controller or viewModel, but simply write it to the result
variable.
And finally, the most important question in using group operations is:
"How can I cancel group operations that contain other group operations?"
Such a case is most conveniently disassembled with the following example:
Our task is to get information about all the user's media content. That is, get all his audio and get all his videos. Let's imagine that this is an extremely laborious process. Because in order to get all the songs, we first have to get the numbers of all albums, then download the songs from each album and only then combine them into one common array.
In order to implement support for canceling nested group operations, we must have such a parameter as progressCancellation
in the methods of their creation.
It should be called in progress blocks of normal network operations.
Then if the main operation is canceled, then implementing progressCancellation
(which is called from normal progress blocks of downloads), nested group operations will be canceled, and then their nested normal network operations...
The property result
and resultClass
were created so that network operations could store the results of their calculations in them, most often there is such a need when they are performed in group operations.
-
The main advantage is the fact that less experienced developers can use network operations successfully. As a result, the development cost is reduced.
-
The second most important advantage is the extreme conciseness compared to other products available on the market. Typically, you will need to write an average of 3 to 33 times less code.
-
The third plus is that the library provides innovative mechanisms that are simply not available in other frameworks, which allows you to manage your network stack without creating a complex architecture.
To write files from temporary directories to permanent ones, it is recommended to use the library FCFileManager.
👨🏼💻 @m1a7
👌🏻 [email protected]