iOS Functional Programming

Posted by tukon on Mon, 24 Jun 2019 19:02:11 +0200

Previous Review

In the previous article, we addressed the first of the last remaining issues in the first article, and in this article we addressed the second remaining issue, how to make the calling process controllable and avoid random calls by the caller.

That is, call as follows:

tool.select(nil).from(@"table").from(@"table").select(nil);

Logically speaking, this code is OK, and it will compile and run without error.But logically speaking, this is obviously wrong and does not conform to the specifications of SQL.

So what needs to be improved is how to control this call process so that only "FROM" can follow "SELECT" and only "WHERE", "JOIN" and so on.

Analysis

Callers are free to call because these properties are declared in the.h file and are public.Since it's public, you can call it anyway.So these attributes cannot be placed in the.h file.How does the caller invoke the property if it is not in the.h file?The answer is Protocol.

With Protocol, we don't care what instance of an object is called, as long as we know what protocol it obeys, we know which methods can be called.

It is estimated that some people will ask, even if the protocol is used and the original properties are put into the protocol, then the caller can call different, what is the difference with not using the protocol?

Indeed, the above question is correct.A class follows a protocol, and to some extent is equivalent to this class with methods declared in the protocol available for external invocation.Therefore, using no protocol does not seem to matter in this scenario.However, if you think about it the other way around, a class that complies with the protocol means that the method in the protocol can be invoked. If it doesn't, then it can't be invoked. (The "Invokable" here is not absolute, but from a normal compilation point of view, there is still a way to invoke private methods.)

That is, multiple protocols are required, and only the corresponding method can be invoked under each protocol.These protocols are not followed in the.h file, but in.m through Class Extension.If you follow it publicly in.h, you're back on the road.In this way, when a method is allowed to be called by the caller, an instance that follows the corresponding protocol is returned; when a method is not allowed to be called, the returned instance does not follow the protocol (which is a bit around ++, let's look directly at the code below).This is what is called Interface-oriented programming (which, to be precise, should be called protocol-oriented programming in iOS).

Protocol Oriented Modification

Referring to the analysis above, we need to move the original attributes into the corresponding protocol, and then as a block of attributes, the return value returns an instance that follows one or more protocols.

Top Code:

.h file

@class SQLTool;

@protocol ISelectable;
@protocol IFromable;
@protocol IJoinable;
@protocol IWhereable;
//GroupBy and so on are omitted here
...

/*****************************/

//Defines a block of select s, and the return value is an instance object that follows the IFromable
//That is, subsequent calls can only call methods of the IFromable protocol
typedef SQLTool <IFromable> *(^Select)(NSArray<NSString *> *columns);

//Define a block from
typedef SQLTool <IJoinable, IWhereable /* GroupBy Such as this is omitted */> *(^From)(NSString *tableName, NSString *alias);

//block defining join
typedef SQLTool <IJoinable, IWhereable /* GroupBy Such as this is omitted */> *(^Join)(NSString *tableName, NSString *alias, NSString *on);

...

/*****************************/

@protocol ISelectable <NSObject>

@property (nonatomic, strong, readonly) Select select;

@end

@protocol IFromable <NSObject>

@property (nonatomic, strong, readonly) From from;

@end

@protocol IJoinable <NSObject>

@property (nonatomic, strong, readonly) Join leftJoin;
@property (nonatomic, strong, readonly) Join rightJoin;
@property (nonatomic, strong, readonly) Join innerJoin;

@end

@interface SQLTool : NSObject

//No need here, put it in.m
//@property (nonatomic, strong, readonly) NSString *sql;

//The parameter is a block, passing an instance of SQLTool that follows ISelectable to the caller
+ (NSString *)makeSQL:(void(^)(SQLTool<ISelectable> *tool))block;

@end

.m file:

//Enable SQLTool to follow all protocols through Class Extension
@interface SQLTool () <ISelectable, IFromable, IJoinable, IWhereable /* GroupBySuch as this is omitted */>

@property (nonatomic, strong) NSString *sql;

@end

@implementation SQLTool

+ (NSString *)makeSQL:(void(^)(SQLTool<ISelectable> *tool))block {
    if (block) {
        //Create an instance that follows the ISelectable protocol and pass it to the caller through a block
        SQLTool<ISelectable> *tool = [[SQLTool<ISelectable> alloc] init];
        block(tool);
        return tool.sql;
    }
    return nil;
}

- (Select)select {
    return ^(NSArray<NSString *> *columns) {
        if (columns.count > 0) {
            self.sql = [NSString stringWithFormat:@"SELECT %@", [columns componentsJoinedByString:@","]];
        } else {
            self.sql = @"SELECT *";
        }
        //Return yourself here
        return self;
    }
}

...

@end

At this point, the nonstandard call mentioned at the beginning of this article (if the code is not misplaced) will not appear.Because each call returns an instance object that only meets some of the protocols, this ensures that only the methods (attributes) declared by those protocols can be called next.

Some summaries

This example is not the best scenario for the application of Interface-oriented programming.To say the most classic application is delegate.With the delegate model, the provider of functionality does not need to know who the current delegate is, as long as it knows that the instance of the delegate complies with a protocol.This can be understood as providing a unified call entry for different instance objects.This should be considered the most typical Interface-oriented application.

In contrast, in this example, the caller knows who the current instance is, but does not know what protocol is being followed.So limit the methods that can be invoked by callers by telling them what protocol they are currently following by returning a value.This can be understood as limiting the invocation behavior of an instance object.

The two seemingly contradictory behaviors are not actually contradictory.It's like everything has two sides. As long as you master its essence, you will find that both sides are right, depending on how you use it.

Last

This example was written with these articles, so there is no so-called complete code at this time.Interested friends can try to finish by themselves.Of course, I will also complete and upload it if there is free follow-up.Welcome to comment.

Thank you for reading!

Topics: SQL Programming iOS