React Native Programming for facets

Implementation in iOS:

The most straightforward way to implement AOP in ObjC is to use Method Swizzling in the Runtime. With Aspects, you don’t need to make tedious manual calls to Method Swizzling.

Application Scenario 1 in iOS: Data statistics

AOP is all about providing a detachable componentization capability to your programs. For example, if your APP needs to use event statistics, whether you use UMeng, Google Analytics, or any other statistics platform, you will probably have written similar code:

- (void)viewDidLoad {
    [super viewDidLoad];
    [Logger log: @"View Did Load"]; // initialize data}Copy the code

Use the Logger class to record a statistical event when the view controller starts loading. The logic of the viewDidLoad method itself is not to do statistics, but to do some initialization. This led to a design flaw where the code for the statistics got mixed up with our actual business logic code. As the business logic code grows, so does the jumble, and such coupling inevitably increases the cost of maintenance. AOP is simply the ability to separate logic such as Logger from the main business logic without affecting the overall functionality of the program. With AOP, our business logic code looks like this:

- (void)viewDidLoad { [super viewDidLoad]; // initialize data}Copy the code

There is no more code for Logger’s statistical logic, but the statistical function is still in effect. Of course, just because you don’t show up in the main business code doesn’t mean the statistical code disappears. Instead, use AOP pattern hook to go somewhere else.

Advantages:

  • 1. Service isolation and decoupling. Separate the main business and statistical business.
  • 2. Plug and play. During pre-release environment and release environment testing, you don’t want to record statistics, just remove the statistics business logic module.
  • 3. If you want to change a statistical platform one day, you don’t need to change the code everywhere, just change the code at the statistical level.

Disadvantages:

  • 1. The code is not intuitive
  • 2. It is difficult to debug the Bug due to improper use

Application Scenario 2 in iOS: Preventing button clicks continuously

There is an article on the Internet iOS– three ways to prevent REPEATED UIButton clicks, after practice found that the article can be used as a demo to demonstrate, in real project development is not practical. Because sendAction:to:forEvent: is UIControl’s method, all classes that inherit from UIControl’s method will be replaced with this method, such as UISwitch. Here is an improved version of this article, making sure that only UIButton changes are hooked:

#import <UIKit/UIKit.h>
@interface UIButton (FixMultiClick)
@property (nonatomic, assign) NSTimeInterval clickInterval;
@end

#import "UIButton+FixMultiClick.h"
#import <objc/runtime.h>
#import <Aspects/Aspects.h>
@interface UIButton ()
@property (nonatomic, assign) NSTimeInterval clickTime;
@end
@implementation UIButton (FixMultiClick)
-(NSTimeInterval)clickTime {
    return [objc_getAssociatedObject(self, _cmd) doubleValue];
}
-(void)setClickTime:(NSTimeInterval)clickTime {
    objc_setAssociatedObject(self, @selector(clickTime), @(clickTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSTimeInterval)clickInterval {
    return [objc_getAssociatedObject(self, _cmd) doubleValue];
}
-(void)setClickInterval:(NSTimeInterval)clickInterval {
    objc_setAssociatedObject(self, @selector(clickInterval), @(clickInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
+(void)load {
    [UIButton aspect_hookSelector:@selector(sendAction:to:forEvent:)
                      withOptions:AspectPositionInstead
                       usingBlock:^(id<AspectInfo> info){
        UIButton *obj = info.instance;
        if(obj.clickInterval <= 0){
            [info.originalInvocation invoke];
        }
        else{
            if ([NSDate date].timeIntervalSince1970 - obj.clickTime < obj.clickInterval) {
                return;
            }
            obj.clickTime = [NSDate date].timeIntervalSince1970;
            [info.originalInvocation invoke];
        }
    } error:nil];
}
@end
Copy the code

Application Scenario 3 in iOS: The NSArray array is out of bounds

There are several specific cases of crash

  • Value: index The value exceeds the index range of array
  • Add: Insert object to nil or Null
  • Insert: Index greater than count, insert object nil or Null
  • Delete: index exceeds the index range of array
  • Replace: Index is out of array index range. Replace object with nil or Null

Solution: HOOK system method, replace the custom security method

#import <Foundation/Foundation.h>
@interface NSArray (Aspect)
@end

#import "NSArray+Aspect.h"
#import <objc/runtime.h>@implementation NSArray (Aspect) /** * replace system methods ** @param systemSelector method to be replaced * @param swizzledSelector method actually used * @param error Error message * * @ during the replacementreturn*/ + (BOOL)systemSelector (SEL)systemSelector customSelector (SEL)swizzledSelector Error (NSError *)error{Method  systemMethod = class_getInstanceMethod(self, systemSelector);if(! systemMethod) {return NO;
    }
    Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
    if(! swizzledMethod) {return NO;
    }
    if (class_addMethod([self class], systemSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
        class_replaceMethod([self class], swizzledSelector, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
    }
    else{
        method_exchangeImplementations(systemMethod, swizzledMethod);
    }
    returnYES; } /** NSArray is a class cluster */ +(void)load{[super load]; // Out of bounds: initialize an empty array [objc_getClass()"__NSArray0") systemSelector:@selector(objectAtIndex:) customSelector:@selector(emptyObjectIndex:) error:nil]; // Out of bounds: initialized non-empty immutable array [objc_getClass("__NSSingleObjectArrayI") systemSelector:@selector(objectAtIndex:) customSelector:@selector(singleObjectIndex:) error:nil]; // Out of bounds: initialized non-empty immutable array [objc_getClass("__NSArrayI") systemSelector:@selector(objectAtIndex:) customSelector:@selector(safe_arrObjectIndex:) error:nil]; // Out of bounds: initialized mutable array [objc_getClass()"__NSArrayM") systemSelector:@selector(objectAtIndex:) customSelector:@selector(safeObjectIndex:) error:nil]; // Out of bounds: uninitialized mutable arrays and uninitialized immutable arrays [objc_getClass("__NSPlaceholderArray") systemSelector:@selector(objectAtIndex:) customSelector:@selector(uninitIIndex:) error:nil]; // Out of bounds: mutable array [objc_getClass()"__NSArrayM") systemSelector:@selector(objectAtIndexedSubscript:) customSelector:@selector(mutableArray_safe_objectAtIndexedSubscript:) error:nil]; // out of bounds vs insert: variable inserts nil, or insert position out of bounds [objc_getClass("__NSArrayM") systemSelector:@selector(insertObject:atIndex:) customSelector:@selector(safeInsertObject:atIndex:) error:nil]; // Insert: nil [objc_getClass()"__NSArrayM") systemSelector:@selector(addObject:)
                               customSelector:@selector(safeAddObject:)
                                          error:nil];
}
- (id)safe_arrObjectIndex:(NSInteger)index{
    if (index >= self.count || index < 0) {
        NSLog(@"this is crash, [__NSArrayI] check index (objectAtIndex:)");return nil;
    }
    return [self safe_arrObjectIndex:index];
}
- (id)mutableArray_safe_objectAtIndexedSubscript:(NSInteger)index{
    if (index >= self.count || index < 0) {
        NSLog(@"this is crash, [__NSArrayM] check index (objectAtIndexedSubscript:)");return nil;
    }
    return [self mutableArray_safe_objectAtIndexedSubscript:index];
}
- (id)singleObjectIndex:(NSUInteger)idx{
    if (idx >= self.count) {
        NSLog(@"this is crash, [__NSSingleObjectArrayI] check index (objectAtIndex:)");return nil;
    }
    return [self singleObjectIndex:idx];
}
- (id)uninitIIndex:(NSUInteger)idx{
    if ([self isKindOfClass:objc_getClass("__NSPlaceholderArray")]) {
        NSLog(@"this is crash, [__NSPlaceholderArray] check index (objectAtIndex:)");return nil;
    }
    return [self uninitIIndex:idx];
}
- (id)safeObjectIndex:(NSInteger)index{
    if (index >= self.count || index < 0) {
        NSLog(@"this is crash, [__NSArrayM] check index (objectAtIndex:)");return nil;
    }
    return [self safeObjectIndex:index];
}
- (void)safeInsertObject:(id)object atIndex:(NSUInteger)index{
    if (index>self.count) {
        NSLog(@"this is crash, [__NSArrayM] check index (insertObject:atIndex:)");return ;
    }
    if (object == nil) {
        NSLog(@"this is crash, [__NSArrayM] check object == nil (insertObject:atIndex:)");return ;
    }
    [self safeInsertObject:object atIndex:index];
}
- (void)safeAddObject:(id)object {
    if (object == nil) {
        NSLog(@"this is crash, [__NSArrayM] check index (addObject:)");return ;
    }
    [self safeAddObject:object];
}
- (id)emptyObjectIndex:(NSInteger)index {
    NSLog(@"this is crash, [__NSArray0] check index (objectAtIndex:)");return nil;
}
@end
Copy the code

validation

- (void)viewDidLoad {
    [super viewDidLoad];
    NSArray *arr1 =  @[@"1"The @"2"];
    NSLog(@"[arr1 objectAtIndex:9527] = %@", [arr1 objectAtIndex:9527]);
    NSLog(@"[arr1 objectAtIndexedSubscript:9527] = %@", [arr1 objectAtIndexedSubscript:9527]);

    NSArray *arr2 =  [[NSArray alloc]init];
    NSLog(@"[arr2 objectAtIndex:9527] = %@", [arr1 objectAtIndex:9527]);
    NSLog(@"[arr2 objectAtIndexedSubscript:9527] = %@", [arr1 objectAtIndexedSubscript:9527]);
    
    NSArray *arr3 =  [[NSArray alloc] initWithObjects:@"1",nil];
    NSLog(@"[arr3 objectAtIndex:9527] = %@", [arr1 objectAtIndex:9527]);
    NSLog(@"[arr3 objectAtIndexedSubscript:2] = %@", [arr3 objectAtIndexedSubscript:2]);

    NSArray *arr4 =  [NSArray alloc];
    NSLog(@"[arr4 objectAtIndex:9527] = %@", [arr4 objectAtIndex:9527]);
    NSLog(@"[arr4 objectAtIndexedSubscript:9527] = %@", [arr4 objectAtIndexedSubscript:9527]);

    NSMutableArray *arr5 =  [NSMutableArray array];
    NSLog(@"[arr5 objectAtIndex:9527] = %@", [arr4 objectAtIndex:9527]);
    NSLog(@"[arr5 objectAtIndexedSubscript:2] = %@", [arr5 objectAtIndexedSubscript:2]);

    NSMutableArray *arr6 =  [NSMutableArray array];
    [arr6 addObject:nil];
    [arr6 insertObject:nil atIndex:4];
    [arr6 insertObject:@3 atIndex:4];
}
Copy the code

Practical introduction to Aspects

Aspects is a third-party library for iOS function substitution based on Method Swizzle, which is a good way to check a Method on a class or an object. Supports method execution before (AspectPositionBefore)/after (AspectPositionAfter) or instead (AspectPositionInstead).

pod "Aspects"
Copy the code

Need to importThe header file:

#import <Aspects/Aspects.h>
Copy the code

External two importantinterfaceThe statement is as follows:

HOOK Specifies methods for all instances of a class

/// add a block of code before/replace/after executing a method of a specified class. Applies to all objects of this class.
///
// Aspectes copies the signature of the @param block method when the hook is added.
/// The first argument will be 'id
      
       ' and the remaining arguments will be arguments to the method being called.
      
/// These parameters are optional and will be used to pass the parameters to the corresponding position of the block.
/// You even use a block with no arguments or only one 'id
      
       ' argument.
      
///
/// @note that adding hooks to static methods is not supported.
/// @return returns a unique value used to cancel the hook.
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;
Copy the code

Second: HOOK the specified method of an instance of a class

/// add a block of code before/replace/after executing a method on a specified object. Applies only to the current object.
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;
Copy the code

Options Options are as follows:

AspectPositionAfter = 0, // Call AspectPositionInstead = 1 after the original method call, // replace the original method AspectPositionBefore = 2, / / in the original method call former executive AspectOptionAutomaticRemoval = 1 < < 3 / / after calling for a clear (can only be used in the object method)Copy the code

Three importantparameterAs follows:

// 1. The metaclass, class, or instance to be hooked
@property (nonatomic.unsafe_unretained.readonly) id instance;
// 2
@property (nonatomic.strong.readonly) NSArray *arguments;
// 3, the original method
@property (nonatomic.strong.readonly) NSInvocation *originalInvocation;
// Execute the original method
[originalInvocation invoke];
Copy the code

The basic use

+ (void)Aspect {
    // Do something after executing viewWillAppear on all instances of the UIViewController class
    [UIViewController aspect_hookSelector:@selector(viewWillAppear:)
                              withOptions:AspectPositionAfter
                               usingBlock:^(id<AspectInfo> info) {
                                   NSString *className = NSStringFromClass([[info instance] class]);
                                   NSLog(@ "% @", className);
                               } error:NULL];
    
    // Do something after instance myVc executes viewWillAppear: method
    UIViewController* myVc = [[UIViewController alloc] init];
    [myVc aspect_hookSelector:@selector(viewWillAppear:)
                            withOptions:AspectPositionAfter
                             usingBlock:^(id<AspectInfo> info) {
                                 id instance = info.instance;               // Call instance object
                                 id invocation = info.originalInvocation;   // The original method
                                 id arguments = info.arguments;             / / parameters
                                 [invocation invoke];                       // The original method is called again
                             } error:NULL];
    // HOOK class method
    Class metalClass = objc_getMetaClass(NSStringFromClass(UIViewController.class).UTF8String);
    [metalClass aspect_hookSelector:@selector(ClassMethod)
                        withOptions:AspectPositionAfter
                         usingBlock:^(id<AspectInfo> info) {
                             NSLog(@ "% @", HOOK class method); } error:NULL];
}
Copy the code

Note:

  • AspectsNot valid for class families, for exampleNSArrayYou need to use a system approach for each subclass individuallyhook.
  • All calls are going to be thread-safe.AspectsUsing theObjective-CWill have a certain performance cost. It is not recommended for calls that are too frequentAspects.AspectsMore suitable for view/controller related code that calls no more than 1000 times per second.
  • When applied to a class (using class methods to add hooks), not bothhookThe same method for parent and child classes; Otherwise it will cause circular call problems. However, when applied to an example of a class (adding hooks using the instance method), this restriction is not applied.
  • When using KVO, it is best to useaspect_hookSelector:Add an observer after the call, otherwise it may cause a crash.

Refer to the link

Ios crash optimization for array out of bounds

Source code analysis of Aspects

Read source code for Aspects

IOS – three implementations to prevent repeated UIButton clicks

Aspects – AN AOP library for faceted programming for iOS

Objc Hack – Some DOS and don ‘ts for Method Swizzle

Aspects – AN AOP library for faceted programming for iOS

Aspect Oriented Programming for iOS

Runtime Method Swizzling mechanism and AOP Programming (section-oriented programming)

Dynamic Block calls (non-parametric blocks)