总结经验

load/initalize/MethodSwizzling/B

2016-06-15  本文已影响156人  js丶

前言

2016年6月7号开始load/initalize/KVO/KVC/Block,并通过代码实现

load/initalize

load 简介

当一个类或分类被添加到Objective-C运行时;会调用load方法,实现这个方法来加载执行特定类的行为。

Discussion:

初始化时执行顺序如下:

注意

使用场景

在自定义实现load方法时,可以很安全地(线程安全)提前获取其他毫无相关类消息,并且在消息还没实现时,手动去实现该消息(也就是说load方法常常应用于Swizzling Method
操作)

initalize 简介

在初始化类之前收到第一个消息,也就是说,对该类进行初始化时被调用

Discussion

+ (void)initialize {
  if (self == [ClassName self]) {
    // ... do the initialization ...
  }
}

使用该方法注意点:

在一个线程安全的方式下进行初始化时,在不同的类initialize被调用的顺序是不保证的,环,使用initialize方法时应尽量做少量且必要的工作。具体而言,initialize方法内部实现了同步锁如果在方法内实现复杂的逻辑,容易会导致死锁。因此,不应该依赖于initialize复杂的初始化,而应该限制它的简单,可以使用init、new等方法实现初始化。

特别注意

initialize每个类只会被调用一次,如果你想为类或分类实现自定义初始化方法可以使用load方法进行Swizzling Method操作,后面会简单讲下FDTemplateLayoutCell框架如何使用Swizzling Method

Method Swizzling 简介

Method Swizzling应用场景

Swizzling应该总是在dispatch_once中执行

Selectors(typedef struct objc_selector *SEL):用于表示在运行时方法的名称。
Method(typedef struct objc_method Method
):表示类的方法
Implementation(typedef id (
IMP)(id, SEL, ...)
): 这个数据类型是指向一个指针函数实现方法

Method Swizzling使用

//
//  UIViewController+MethodSwizzlingCategoryExample.m
//  LoadInitalizeKVOKVCBlockExample
//
//  Created by lmj  on 16/6/8.
//  Copyright © 2016年 linmingjun. All rights reserved.
//

#import "UIViewController+MethodSwizzlingCategoryExample.h"
#import <objc/runtime.h>
@implementation UIViewController (MethodSwizzlingCategoryExample)


+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken,^{
        Class class= [self class];
        
        SEL originalSelector = @selector(viewDidLoad);
        SEL swizzledSelector = @selector(swizzledViwDidLoad);
        // 通过class_getInstanceMethod()函数从当前class对象中的method list获取method结构体
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        
        // 如果是类方法:
        // Class class = object_getClass((id)self);
        // ...
        // Method originalMethod = class_getClassMethod(class, originalSelector);
        // Method swizzledMethod = class_getClassMethod(class, swizzledSelector);
        
        // 使用class_addMethod()函数对Method Swizzling做了一层验证,如果self没有实现swizzledSelector交换的方法,会导致失败
        BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        
        if (didAddMethod) { // 如果self没有实现swizzledViwDidLoad方法,class_replaceMethod向对象所属的类动态添加所需的selector:,如果swizzledSelector没有实现,
            
            
            
            // class_replaceMethod,它有两种不同的行为。当类中没有想替换的原方法时,该方法会调用class_addMethod来为该类增加一个新方法,也因为如此,class_replaceMethod在调用时需要传入types参数,而method_exchangeImplementations和method_setImplementation却不需要。
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {// 通过class_addMethod()的验证,如果self实现了swizzledViwDidLoad这个方法,class_addMethod()函数将会返回NO,进行交换了。
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
        
        
    });
    
}

#pragma mark - Method Swizzling

- (void)swizzledViwDidLoad {
    // 在swizzledViwDidLoad方法内部调用[self swizzledViwDidLoad];时,执行的是UIViewController的viewDidLoad方法。
    // 该方法调用 [self swizzledViwDidLoad]; 不会造成死循环;因为swizzledViwDidLoad已经被指定为viewDidLoad方法,所以这里实质调用的是[self viewDidLoad];方法,经过调试如果不使用这行代码程序运行结果相同,但是 Mattt Thompson指出这行代码是为了防止潜在的危险或麻烦,体现一个优秀程序员的良好素养.
    [self swizzledViwDidLoad];
    NSLog(@"swizzledViwDidLoad: %@", self);
}

@end

 SEL originalSelector = @selector(viewDidLoad);
 SEL swizzledSelector = @selector(swizzledViwDidLoad);

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

1.通过class_getInstanceMethod()函数从当前class对象中的method list获取method结构体

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

2.使用class_addMethod()函数对Method Swizzling做了一层验证,如果ViewController没有viewDidLoad(书写过程可能写成viewDidLoadd等错误),swizzledSelector交换的方法,会导致失败,返回YES

method_exchangeImplementations(originalMethod, swizzledMethod);

3.通过class_addMethod()的验证,如果self实现了swizzledViwDidLoad这个方法,class_addMethod()函数将会返回NO,进行交换了。

swizzledViwDidLoad方法内调用[self swizzledViwDidLoad]原因:

FDTemplateLayoutCell使用Method Swizzling

+ (void)load {
    // All methods that trigger height cache's invalidation(触发高度缓存失效的所有方法)
    SEL selectors[] = {
        @selector(reloadData),
        @selector(insertSections:withRowAnimation:),
        @selector(deleteSections:withRowAnimation:),
        @selector(reloadSections:withRowAnimation:),
        @selector(moveSection:toSection:),
        @selector(insertRowsAtIndexPaths:withRowAnimation:),
        @selector(deleteRowsAtIndexPaths:withRowAnimation:),
        @selector(reloadRowsAtIndexPaths:withRowAnimation:),
        @selector(moveRowAtIndexPath:toIndexPath:)
    };
    // sizeof(selectors) / sizeof(SEL) =  (9(数组大小))* SEL  / SEL; 一种用C语言计算个数的常用的技巧
    for (NSUInteger index = 0; index < sizeof(selectors) / sizeof(SEL); ++index) {
        SEL originalSelector = selectors[index];
        
        SEL swizzledSelector = NSSelectorFromString([@"fd_" stringByAppendingString:NSStringFromSelector(originalSelector)]);
        Method originalMethod = class_getInstanceMethod(self, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
     
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

block

本章介绍了块和变量之间的相互作用,包括内存管理

block变量的类型

在block作为对象代码中,变量可以用五种不同的方式处理,其中有是三种标准类型的变量,如同一个函数:

block还支持其他两种类型的变量

以下规则适用于block中使用变量

- (void)blockExample1 {

    int x = 123;
     
    void (^printXAndY)(int) = ^(int y) {
     
        printf("%d %d\n", x, y);
    };
     
    printXAndY(456); 

}
// prints: 123 456

局部非静态变量试图将一个的值赋给块内作为一个新值会导致一个错误:

- (void)blockExample2 {

    int x = 123;
     
    void (^printXAndY)(int) = ^(int y) {
     
        x = x + y; // error
        printf("%d %d\n", x, y);
    };

}
// Error

下面的例子说明了如何使用一个__block变量:

__block int x = 123; //  x lives in block storage
 
void (^printXAndY)(int) = ^(int y) {
 
    x = x + y;
    printf("%d %d\n", x, y);
};
printXAndY(456); // prints: 579 456
// x is now 579

下面的例子显示了几个类型的变量的相互作用:

对象和block变量

作为block变量提供Objective-C和C++对象和其他block的支持

Objective-C 对象

1.如果你通过引用访问的实例变量,那么这个block会对self产生强引用。
2.如果你通过值来访问实例变量,那么这个block会对这个变量本身产生强引用。

下面的例子说明了这两种不同的情况

dispatch_async(queue, ^{
    // instanceVariable is used by reference, a strong reference is made to self
    doSomethingWithObject(instanceVariable);
});
 
 
id localVariable = instanceVariable;
dispatch_async(queue, ^{
    /*
      localVariable is used by value, a strong reference is made to localVariable
      (and not to self).
    */
    doSomethingWithObject(localVariable);
});


dispatch_async(queue, ^{
    // instanceVariable is used by reference, a strong reference is made to self
    doSomethingWithObject(instanceVariable);
});
 
 
id localVariable = instanceVariable;
dispatch_async(queue, ^{
    /*
      localVariable is used by value, a strong reference is made to localVariable
      (and not to self).
    */
    doSomethingWithObject(localVariable);
});

dispatch_async(queue, ^{

    // instanceVariable is used by reference, a strong reference is made to self
    doSomethingWithObject(instanceVariable);
});
 
 
id localVariable = instanceVariable;
dispatch_async(queue, ^{
    /*
      localVariable is used by value, a strong reference is made to localVariable
      (and not to self).
    */
    doSomethingWithObject(localVariable);
});

C ++对象

调用一个block

如果你声明一个block作为一个变量,可以使用它作为一个函数,如下两个例子的使用

int (^oneFrom)(int) = ^(int anInt) {
    return anInt - 1;
};
 
printf("1 from 10 is %d", oneFrom(10));
// Prints "1 from 10 is 9"
 
float (^distanceTraveled)(float, float, float) =
                         ^(float startingSpeed, float acceleration, float time) {
 
    float distance = (startingSpeed * time) + (0.5 * acceleration * time * time);
    return distance;
};
 
float howFar = distanceTraveled(0.0, 9.8, 1.0);
printf:  howFar:4.9

通常,你将一个block作为一个函数或一个方法的参数传递给一个block。在这些情况下,通常创建一个“inline(内联)”块。

使用block作为函数参数

你可以通过一个block作为一个函数参数,如同其他参数一样。然而,在很多情况下,你不需要声明block;相反,你只需简单地实现它们,然后使用inline(内联)它们作为一个参数。下面的例子使用了qsort_b功能。qsort_b类似于标准的qsort_r功能,以最后参数作为一个block。

char *myCharacters[3] = { "TomJohn", "George", "Charles Condomine" };
 
qsort_b(myCharacters, 3, sizeof(char *), ^(const void *l, const void *r) {
    char *left = *(char **)l;
    char *right = *(char **)r;
    return strncmp(left, right, 1);
});
// Block implementation ends at "}"
 
// myCharacters is now { "Charles Condomine", "George", "TomJohn" }

下面的例子演示了如何使用block与dispatch_apply功能。 dispatch_apply声明如下:

void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));

功能块用于提交多次调用调度队列。它需要三个参数,第一个指定的迭代次数来执行;二是指定一个被提交的队列,第三个是block本身,而这个block本身又需要一个单一的参数作为当前的迭代次数。
可以使用dispatch_apply打印出迭代索引

#include <dispatch/dispatch.h>
size_t count = 10;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 
dispatch_apply(count, queue, ^(size_t i) {
    printf("%u\n", i);
});
printf: 0
        2
        1
        3
        4
        5
        6
        7
        8
        9

使用block作为方法参数

Cocoa提供了一种方法使用block的数量。 你通过block作为一个方法的参数就像任何其他参数。

NSArray *array = @[@"A", @"B", @"C", @"A", @"B", @"Z", @"G", @"are", @"Q"];
NSSet *filterSet = [NSSet setWithObjects: @"A", @"Z", @"Q", nil];
 
BOOL (^test)(id obj, NSUInteger idx, BOOL *stop);
 
test = ^(id obj, NSUInteger idx, BOOL *stop) {
 
    if (idx < 5) {
        if ([filterSet containsObject: obj]) {
            return YES;
        }
    }
    return NO;
};
 
NSIndexSet *indexes = [array indexesOfObjectsPassingTest:test];
 
NSLog(@"indexes: %@", indexes);
 
/*
Output:
indexes: <NSIndexSet: 0x10236f0>[number of indexes: 2 (in 2 ranges), indexes: (0 3)]
*/

拷贝block方法

你可以copy和release模块:
也可以使用C函数,如下两个函数

Block_copy();

Block_release();

为了避免内存泄漏,必须始终平衡Block_copy()
与Block_release()两种操作

避免两种block书写

避免block在for/while循环和判断语句内 (that is, ^{ ... }),,表示该block的是局部数据结构的地址,栈的局部数据结构的范围是封闭的复合语句,所以你应该避免在下面的例子中显示的模式:

面试提问:

load 与 initialize 的区别
答:load :
(1)load方法在这个文件被程序装载时调用 (2)如果一个类实现了load
方法,在调用这个方法前会首先调用父类的load (3)如果一个类没有实现load
方法,那么就不会调用它父类的load方法(4)添加一个子类的分类时,在分类添加load方法时,调用顺序parent->child->category(5)在Compile Sources中,文件的排放顺序就是其装载顺序,自然也就是load方法调用的顺序(除了父子类关系的描述)
(6)load方法是线程安全的,它内部使用了锁,所以我们应该避免线程阻塞在load
方法中。
initialize:(1)实例化一个对象时调用,只会调用一次(2)在initialize
方法内部也会调用父类的方法,即使子类没有实现initialize方法,也会调用父类的方法

上一篇 下一篇

猜你喜欢

热点阅读