Developing runtime applications using IOS

Time:2021-12-7

1. Function of runtime

  • Dictionary to model
  • Dynamically modify member variables
  • Method exchange
  • Add attributes to classification

2. Dictionary to model

#import "NSObject+Json.h"
#import <objc/runtime.h>
#import <objc/message.h>

@implementation NSObject (Json)

+ (instancetype)xwx_initWithDictionaryForModel:(NSDictionary *)dic{
    
    id myObj = [[self alloc] init];
    unsigned int outCount;
    //Gets all member properties in the class
    objc_property_t *arrPropertys = class_copyPropertyList([self class], &outCount);
    
    for (NSInteger i = 0; i < outCount; i ++) {
        
        //Get property name string
        objc_property_t property = arrPropertys[i];
        //Property name in model
        NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
        id propertyValue = dic[propertyName];
        
        //Handle the problem that the server field does not match the local model field
        If ([propertyname isequaltostring: @ "Age3"]) {// handle the problem of mismatch between local and network characters
            propertyValue = dic[@"age"];
        }
    
        if (propertyValue != nil) {
            [myObj setValue:propertyValue forKey:propertyName];
        }
    }
    //Note that not all arc Objective-C objects need to be released when obtaining attributes at runtime
    free(arrPropertys);
    return myObj;
}
@end

3. Dynamically modify member variables

#import "UITextField+PlaceHolder.h"
#import <objc/runtime.h>

@implementation UITextField (PlaceHolder)

-(void)changePlaceHolderTextColor:(UIColor *)color{

//This is to print out all member variables and get the name of the member variable
    unsigned int count;
    //Gets all member variables in the uitextfield
    Ivar *ivars = class_copyIvarList([UITextField class], &count);
    for (int i = 0; i < count; i++) {
    //Get the member variable at I position
    Ivar ivar = ivars[i];
   //Nslog (@ "member variable% s", ivar_getname (Ivar));
    }
    free(ivars);
     
    //IOS 13 modifies private properties through KVC. There is a crash risk. Use it carefully! Not all kvcs will crash. Try!
    if ([[UIDevice currentDevice].systemVersion floatValue] > 13.0) {
        //Get a member variable according to its name
        Ivar ivar =  class_getInstanceVariable([UITextField class], "_placeholderLabel");
        //object_ Getivar gets the value of the member variable
        UILabel *placeholderLabel = object_getIvar(self, ivar);
        placeholderLabel.textColor = color;

    }else{
        //KVC assignment. You can use KVC assignment before ios13
        [self setValue:color forKeyPath:@"_placeholderLabel.textColor"];
  }
}
@end
  • Requirement 2: add placeholder to uitextview
#import "UITextView+PlaceHolder.h"
#import <objc/runtime.h>

@implementation UITextView (PlaceHolder)



-(void)SetPlaceHolderTextColor:(UIColor *)color fontSize:(CGFloat)font textContent:(NSString *)text{
    
    
    UILabel *placeHolderLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 30)];
       placeHolderLabel.text = text;
       placeHolderLabel.numberOfLines = 0;
       placeHolderLabel.textColor = color;
    placeHolderLabel.font = [UIFont systemFontOfSize:font];
       [placeHolderLabel sizeToFit];
       
       if (self.text.length == 0) {
           [self addSubview:placeHolderLabel];// This sentence is very heavy. Do you want to forget it
       }
       
    [self setValue:placeHolderLabel forKey:@"_placeholderLabel"];

}

@end

It is worth noting that after ios13, using KVC to assign values to member variables may crash. Try again

4. Method exchange

  • Requirement 1 nsmutablearray * array [array insertobject: Nil atindex: 0] prevents crash

Reference linkhttps://www.jianshu.com/p/e0d46032b27f

//
//  NSMutableArray+Extension.m
//  runtime
//
//  Created by eport on 2020/12/13.


#import "NSMutableArray+Extension.h"
#import <objc/runtime.h>

//Method exchange
@implementation NSMutableArray (Extension)


//Class methods are called only once at runtime, and. + (void) load is called at app runtime. The method will be called once as long as the project type is added, compiled, and the method is implemented in. M. It is worth noting that subclasses that are not implemented will not be called, even if the parent class is implemented
+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //Class cluster: nsstring, nsarray, nsdictionary. The real type is other types
        Class cls = NSClassFromString(@"__NSArrayM");
        Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
        Method method2 = class_getInstanceMethod(cls, @selector(jf_insertObject:atIndex:));
        method_exchangeImplementations(method1, method2);
    });
}

- (void)jf_insertObject:(id)anObject atIndex:(NSUInteger)index{

    if (anObject == nil) return;// If it is an empty object, it will not be added to prevent collapse

    //After interception, call the implementation of the system. Because you don't know the underlying implementation logic, you may make all kinds of unexpected mistakes in implementing the system method yourself
    [self jf_insertObject:anObject atIndex:index];// It seems to be an endless loop. The logic and method implementation have been exchanged
}

@end
  • Demand 2 access times of print controller

Reference link://https://www.jianshu.com/p/6bcff1f9feee

#import "UIViewController+Logging.h"
#import <objc/runtime.h>

@implementation UIViewController (Logging)

+ (void)load
{
    swizzleMethod([self class], @selector(viewDidAppear:), @selector(swizzled_viewDidAppear:));
}

- (void)swizzled_viewDidAppear:(BOOL)animated
{
    // call original implementation
    [self swizzled_viewDidAppear:animated];
    
    // Logging
    NSLog(@"%@", NSStringFromClass([self class]));
}

void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector)
{
    // the method might not exist in the class, but in its superclass
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    
    // class_addMethod will fail if original method already exists
    BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
    
    // the method doesn’t exist and we just added one
    if (didAddMethod) {
        class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    }
    else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
    
}
@end

  • Requirement 3: it is required that the buttons in the project cannot be clicked repeatedly
#import "UIButton+DelaySwizzling.h"
#import <objc/runtime.h>

@interface UIButton()

//Repeat click interval
@property (nonatomic, assign) NSTimeInterval xxx_acceptEventInterval;
//Last click timestamp
@property (nonatomic, assign) NSTimeInterval xxx_acceptEventTime;

@end


@implementation UIButton (DelaySwizzling)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(sendAction:to:forEvent:);
        SEL swizzledSelector = @selector(xxx_sendAction:to:forEvent:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        BOOL didAddMethod = class_addMethod(class,
                                            originalSelector,
                                            method_getImplementation(swizzledMethod),
                                            method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)xxx_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    
    //If you want to set a uniform interval, you can add the following sentences here
    if (self.xxx_acceptEventInterval <= 0) {
        //If there is no custom time interval, the default is 0.4 seconds
        self.xxx_acceptEventInterval = 0.4;
    }
    
    //Is it less than the set time interval
    BOOL needSendAction = (NSDate.date.timeIntervalSince1970 - self.xxx_acceptEventTime >= self.xxx_acceptEventInterval);
    
    //Update last click timestamp
    if (self.xxx_acceptEventInterval > 0) {
        self.xxx_acceptEventTime = NSDate.date.timeIntervalSince1970;
    }
    
    //The response event is executed only when the time interval between two clicks is less than the set time interval
    if (needSendAction) {
        [self xxx_sendAction:action to:target forEvent:event];
    }
}

- (NSTimeInterval )xxx_acceptEventInterval{
    return [objc_getAssociatedObject(self, "UIControl_acceptEventInterval") doubleValue];
}

- (void)setXxx_acceptEventInterval:(NSTimeInterval)xxx_acceptEventInterval{
    objc_setAssociatedObject(self, "UIControl_acceptEventInterval", @(xxx_acceptEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSTimeInterval )xxx_acceptEventTime{
    return [objc_getAssociatedObject(self, "UIControl_acceptEventTime") doubleValue];
}

- (void)setXxx_acceptEventTime:(NSTimeInterval)xxx_acceptEventTime{
    objc_setAssociatedObject(self, "UIControl_acceptEventTime", @(xxx_acceptEventTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

5. Add attribute to classification

  • Requirement 1: add attributes to category

Article referencehttps://www.jianshu.com/p/916aef6f7ab1

//. h file
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface NSObject (AssociatedObject)

@property(nonatomic,copy)NSString *associatedObject;

@end

NS_ASSUME_NONNULL_END
//. m file
import "NSObject+AssociatedObject.h"
#import <objc/runtime.h>

@implementation NSObject (AssociatedObject)

- (void)setAssociatedObject:(id)associatedObject
{
    //Note the type of the last parameter
    objc_setAssociatedObject(self, @selector(associatedObject), associatedObject, OBJC_ASSOCIATION_COPY);
}

- (id)associatedObject
{
    return objc_getAssociatedObject(self, _cmd);
}



@end

Requirement 2: add a block to uibutton to handle click events
(1) Writing method 1

#import <UIKit/UIKit.h>
#Import < objc / runtime. H > // import header file

//Declare a callback block for a button click event
typedef void(^ButtonClickCallBack)(UIButton *button);

@interface UIButton (Handle)

//Callback method added for uibutton
- (void)handleClickCallBack:(ButtonClickCallBack)callBack;

@end
#import "UIButton+Handle.h"

//Declare a static index key to get the value of the associated object
static char *buttonClickKey;

@implementation UIButton (Handle)

- (void)handleClickCallBack:(ButtonClickCallBack)callBack {
    //Associate the instance of button with the callback block through the index key:
    objc_setAssociatedObject(self, &buttonClickKey, callBack, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    //Set the method of button execution
    [self addTarget:self action:@selector(buttonClicked) forControlEvents:UIControlEventTouchUpInside];
}

- (void)buttonClicked {
    //Get the associated object through the static index key (here is the callback block)
    ButtonClickCallBack callBack = objc_getAssociatedObject(self, &buttonClickKey);
    
    if (callBack) {
        callBack(self);
    }
}

@end

(2) Writing method 2

#import <UIKit/UIKit.h>
#import <objc/runtime.h>  

typedef void(^ButtonClickCallBack)(UIButton * _Nullable button);

NS_ASSUME_NONNULL_BEGIN

@interface UIButton (Handler)

@property(nonatomic,copy)ButtonClickCallBack  bottonCallBack;

@end

NS_ASSUME_NONNULL_END
#import "UIButton+Handler.h"


@implementation UIButton (Handler)

- (void)setCallBlock:(ButtonClickCallBack)bottonCallBack
{
    objc_setAssociatedObject(self, @selector(callBlock), bottonCallBack, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (ButtonClickCallBack )callBlock
{
    return objc_getAssociatedObject(self, _cmd);
    //    return objc_getAssociatedObject(self, @selector(callBlock));
}



@end