Migrate a bunch of old articles to nuggets

VKMarkHelper making address

Preface – This is just a working note

In fact, there is no technical content in this, but the starting point is that there are more and more requirements for similar marker bits in the work, so I want to write a tool to deal with it.

When writing tools, they are not satisfied with the simple design of a general API at the beginning. When extending each specific tag usage scenario, they also bother to call API for secondary writing code (although it is only a few lines, they are too lazy to write), so they try to achieve it as dynamically as possible.

Want the user to just give the tag a name, and nothing else

Requirements that could not be simpler

  • About the first special treatment:

    • New function module online, to mark the red dot to remind users, users click after disappear
    • Novice guide, to the user the first time to open the function when the show, and then open not to show
    • Ash!
  • About the special treatment once a day:

    • Daily mission, first time of day for special effects
    • Tips and Toast Tips, only once a day
    • Ash!

Well, you just write a little bit, write it locally, and read the little bit to see if it’s been clicked before, if it’s clicked today.

I won’t go into details. It’s not technical.

How can I generate this code automatically?

Generic API design that still feels cumbersome

I’m going to do my best to design the code that writes the tag, that reads the tag, into a generic API, passing in the tag name as a parameter

+ (BOOL)isFunctionShowOnce:(NSString *)functionMark;// Determine whether a flag bit is marked+ (void)markFunctionShowOnce:(NSString *)functionMark;// Mark a flag bit
Copy the code

But what if I wanted to design an integral function for beginners to boot?

I’m going to write these two functions by hand

+ (BOOL)isScoreShowOnce;// Check whether the integral is marked+ (void)markScoreShowOnce;// Label the integral
Copy the code

I’m also going to hand write a tag bit name

static const NSString *ScoreMark = @"ScoreMark"
Copy the code

Then call the generic API

[self isFunctionShowOnce:ScoreMark];
[self markFunctionShowOnce:ScoreMark];
Copy the code

What if we were to develop a lottery feature tomorrow? One more time…

What if we were to develop a check-in feature the day after tomorrow? I’m gonna do it again…

I was just trying to make it easy

As I said earlier, I just wanted to write a tag bit name, ScoreMark, and everything would be done.

And in the interface where I really want to call ScoreMark isScoreShowOnce and markScoreShowOnce, Xcode will automatically prompt and complete as if I had actually written this function.

Use macros to declare functions

#define FirstLaunchVKMark(name)\+ (void)setFirstLaunchVKMark##name; \+ (BOOL)isFirstLaunchedVKMark##name; \
Copy the code

This macro is a function declaration macro that automatically generates two function names suffixed with name as the parameter name. The ## operator concatenates the parameter as a character with the preceding function name.

@interface VKMarkHelper (THIS)

FirstLaunchVKMark(gogogo);

@end
Copy the code

Writing a FirstLaunchVKMark(gogogo) in the header file with the help of a macro is equivalent to declaring two functions

+ (void)setFirstLaunchVKMarkgogogo; + (BOOL)isFirstLaunchedVKMarkgogogo;
Copy the code

This way, with the function declaration in the header file, Xcode is completely auto-complete when called, and part of our goal is complete

Use message forwarding to handle logic dynamically

Function declarations alone are meaningless, forcing calls without writing the logic would result in an unrecognized selectorcrash

And this realization logic, or the sentence, I still too lazy to write, hope to be completed automatically. How to do? Runtime message forwarding.

Message forwarding means that the Runtime still has 3 chances to prevent crash before an unrecognized selector occurs, and 3 chances are that 3 sets of invalid messages will try to forward the remedial measures to take effect again, and the relevant content searches for message forwarding keywords, so I’m not going to explain in detail

ResolveClassMethod: and resolveInstanceMethod: are the first opportunity to remedy the situation. Since my case uses class methods, I use the former.

First remedy: When a function cannot be called, the Runtime sends a message resolveClassMethod:, in which code can be used to dynamically create a function that does not exist. If the operation is caught by the code and some action is taken, the Runtime attempts to re-send the message. Will crash

+ (BOOL)resolveClassMethod:(SEL)sel
{
    NSString *selstr = NSStringFromSelector(sel);
    if ([selstr rangeOfString:@"VKMark"].location ! =NSNotFound) {
        Class clazz = [self class];
        Class metaClazz = object_getClass(clazz);
        
        if ([selstr rangeOfString:@"set"].location ! =NSNotFound) {
            class_addMethod(metaClazz, sel, (IMP) dynamicVKMarkSetterIMP, "v@:");
        }
        
        if ([selstr rangeOfString:@"is"].location ! =NSNotFound) {
            class_addMethod(metaClazz, sel, (IMP) dynamicVKMarkBoolGetterIMP, "b@:");
        }
        
        
        return YES;
    }else
    {
        return NO; }}Copy the code

ResolveClassMethod: resolves the invalid selector by using the name string to determine whether I added the selector using the macro. If so, add the selector dynamically by using the code class_addMethod. It points to the prepared dynamicVKMarkSetterIMP, and returns YES to tell runtime that I have executed this remedy, please resend it.

void dynamicVKMarkSetterIMP(id self, SEL _cmd)
{
    // implementation ....
    NSString *selstr = NSStringFromSelector(_cmd);
    if ([selstr rangeOfString:@"setFirstLaunchVKMark"].location ! =NSNotFound) {
        NSString * name = [selstr substringFromIndex:@"setFirstLaunchVKMark".length];
        [VKMarkHelper setFirstLaunchVKMark:name];
    }
    if ([selstr rangeOfString:@"setTodayShowOnceVKMark"].location ! =NSNotFound) {
        NSString * name = [selstr substringFromIndex:@"setTodayShowOnceVKMark".length];
        [VKMarkHelper setTodayShowOnceWithVKMark:name];
    }
    return;
}
Copy the code

So this function, by taking a string extract from the selector, is going to extract the name of the argument that we used when we wrote the macro, and with that name, we’re going to be able to call that generic API as usual

One other thing to note is that we are dealing with class methods, and adding methods directly to [self class] with class_addMethod is actually adding instance methods. If we add methods in this way, we will still crash even if the runtime resends because we did not successfully add the desired class method.

So Class metaClazz = object_getClass(clazz); Is very important, through the metaclass metaClazz capturing the object_getClass class, on the metaClazz class_addMethod, can be added on the class methods.

Lazy done

Yeah, work notes are over