学习资源收集

ProtocolKit 解读

2019-11-28  本文已影响0人  01_Jack

Swift支持协议方法的默认实现,而Objective-C不支持,突然想到多年前sunnyxx开源的 ProtocolKit ,顺手翻翻源码。


Protocol Extension

从使用来看ProtocolKit的实现似乎全靠@defs,据唐巧这篇文章所言,@defs可以用来返回一个与当前Objective-C具有相同内存布局的struct。遗憾的是,当前架构与平台不支持了,这应该是__OBJC2__之前的产物。

@defs

虽然不支持,但是可以看到,@defs在代码中仍被标识为关键字颜色,而ProtocolKit重定义了defs:

// For a magic reserved keyword color, use @defs(your_protocol_name)
#define defs _pk_extension

// Interface
#define _pk_extension($protocol) _pk_extension_imp($protocol, _pk_get_container_class($protocol))

// Implementation
#define _pk_extension_imp($protocol, $container_class) \
    protocol $protocol; \
    @interface $container_class : NSObject <$protocol> @end \
    @implementation $container_class \
    + (void)load { \
        _pk_extension_load(@protocol($protocol), $container_class.class); \
    } \

// Get container class name by counter
#define _pk_get_container_class($protocol) _pk_get_container_class_imp($protocol, __COUNTER__)
#define _pk_get_container_class_imp($protocol, $counter) _pk_get_container_class_imp_concat(__PKContainer_, $protocol, $counter)
#define _pk_get_container_class_imp_concat($a, $b, $c) $a ## $b ## _ ## $c

void _pk_extension_load(Protocol *protocol, Class containerClass);
  1. defs替换成_pk_extension
  2. Interface部分_pk_extension中传入protocol又通过一系列的宏定义来完成中间类的声明与实现
  3. Implementation部分在中间类调用load时,通过_pk_extension_load函数生成所需数据
  4. 通过Get container class name by counter部分的宏定义获取中间类
  5. _pk_extension_load生成所需数据

先来看看被中间类是如何获取的,在这里出现了一个宏__COUNTER__

#define a printf("%d\n", __COUNTER__)
#define b printf("%d\n", __COUNTER__)

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        a; b;
        printf("%d\n", __COUNTER__);
        printf("%d\n", __COUNTER__);
    }
    return 0;
}
__COUNTER__

再举个例子:

#define a printf("%d\n", __COUNTER__)
#define b printf("%d\n", __COUNTER__)

@interface A : NSObject
@end

@implementation A
+ (void)counter0 {
    printf("%d\n", __COUNTER__);
}
+ (void)counter1 {
    printf("%d\n", __COUNTER__);
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        a; b;
        printf("%d\n", __COUNTER__);
        printf("%d\n", __COUNTER__);
    }
    return 0;
}
__COUNTER__

注意:__COUNTER__的数值与调用次数无关,仅与编译相关。这里不管A是否调用counter0与counter1,不管调用了一次还是多次,在其内部的数值都是固定的0和1。

#define _pk_get_container_class($protocol) _pk_get_container_class_imp($protocol, __COUNTER__)
#define _pk_get_container_class_imp($protocol, $counter) _pk_get_container_class_imp_concat(__PKContainer_, $protocol, $counter)
#define _pk_get_container_class_imp_concat($a, $b, $c) $a ## $b ## _ ## $c

以文章开头的Forkable 协议为例,这里最终调用_pk_get_container_class_imp_concat时,对应的参数分别为__PKContainer_Forkable0,宏替换后得到类名__PKContainer_Forkable_0

调用@defs(Forkable)时,变为_pk_extension_imp (Forkable, __PKContainer_Forkable_0),宏展开后的代码为:

@protocol Forkable;
@interface __PKContainer_Forkable_0 : NSObject <Forkable> @end
@implementation __PKContainer_Forkable_0
+ (void)load {
    _pk_extension_load(@protocol(Forkable), __PKContainer_Forkable_0.class);
}

显然展开后的代码缺少@end,回头看看Demo用法:

@defs(Forkable)
- (void)fork {
    NSLog(@"Forkable protocol extension: I'm forking (%@).", self.github);
}
- (NSString *)github {
    return @"This is a required method, concrete class must override me.";
}
@end

@end在这里补齐了,由于protocol前没加@,所以使用defs时需要加@,由于宏展开后缺少@end,所以@defs必须与@end配对出现。

typedef struct {
    Protocol *__unsafe_unretained protocol;  // 协议
    Method *instanceMethods;  // 实例方法实现列表
    unsigned instanceMethodCount;  // 实现的实例方法个数
    Method *classMethods;  // 类方法实现列表
    unsigned classMethodCount;  // 实现的类方法个数
} PKExtendedProtocol;

static PKExtendedProtocol *allExtendedProtocols = NULL;
static pthread_mutex_t protocolsLoadingLock = PTHREAD_MUTEX_INITIALIZER;
static size_t extendedProtcolCount = 0, extendedProtcolCapacity = 0;

通过allExtendedProtocols来存储协议与对应的默认实现,通过protocolsLoadingLock确保线程安全,通过extendedProtcolCount与extendedProtcolCapacity对allExtendedProtocols扩容。

扩容代码如下:

   if (extendedProtcolCount >= extendedProtcolCapacity) {
        size_t newCapacity = 0;
        if (extendedProtcolCapacity == 0) {
            newCapacity = 1;
        } else {
            newCapacity = extendedProtcolCapacity << 1;
        }
        allExtendedProtocols = realloc(allExtendedProtocols, sizeof(*allExtendedProtocols) * newCapacity);
        extendedProtcolCapacity = newCapacity;
    }

逻辑很简单,首次使用容量为1,以后每当容量不足时扩容2倍。

void _pk_extension_load(Protocol *protocol, Class containerClass) {
    
//    加锁
    pthread_mutex_lock(&protocolsLoadingLock);
    
//    扩容
    if (extendedProtcolCount >= extendedProtcolCapacity) {
        size_t newCapacity = 0;
        if (extendedProtcolCapacity == 0) {
            newCapacity = 1;
        } else {
            newCapacity = extendedProtcolCapacity << 1;
        }
        allExtendedProtocols = realloc(allExtendedProtocols, sizeof(*allExtendedProtocols) * newCapacity);
        extendedProtcolCapacity = newCapacity;
    }
    
//    寻找传入protocol所在allExtendedProtocols中的位置
    size_t resultIndex = SIZE_T_MAX;
    for (size_t index = 0; index < extendedProtcolCount; ++index) {
        if (allExtendedProtocols[index].protocol == protocol) {
            resultIndex = index;
            break;
        }
    }
    
//    如果没找到,新建struct并存入allExtendedProtocols中
    if (resultIndex == SIZE_T_MAX) {
        allExtendedProtocols[extendedProtcolCount] = (PKExtendedProtocol){
            .protocol = protocol,
            .instanceMethods = NULL,
            .instanceMethodCount = 0,
            .classMethods = NULL,
            .classMethodCount = 0,
        };
        resultIndex = extendedProtcolCount;
        extendedProtcolCount++;
    }
    
//    合并protocol方法实现
    _pk_extension_merge(&(allExtendedProtocols[resultIndex]), containerClass);

//    解锁
    pthread_mutex_unlock(&protocolsLoadingLock);
}
  1. 通过protocolsLoadingLock确保allExtendedProtocols线程安全
  2. 对allExtendedProtocols扩容
  3. 寻找传入protocol在allExtendedProtocols中的位置
  4. 如果没找到,新建struct并存入allExtendedProtocols中
  5. 合并protocol方法实现

_pk_extension_load中调用_pk_extension_merge对protocol方法进行合并,而真正的合并逻辑在_pk_extension_create_merged中:

Method *_pk_extension_create_merged(Method *existMethods, unsigned existMethodCount, Method *appendingMethods, unsigned appendingMethodCount) {
    
    if (existMethodCount == 0) {
        return appendingMethods;
    }
    unsigned mergedMethodCount = existMethodCount + appendingMethodCount;
    Method *mergedMethods = malloc(mergedMethodCount * sizeof(Method));
    memcpy(mergedMethods, existMethods, existMethodCount * sizeof(Method));
    memcpy(mergedMethods + existMethodCount, appendingMethods, appendingMethodCount * sizeof(Method));
    return mergedMethods;
}

existMethodsexistMethodCount对应着struct中的Methods与count,appendingMethodsappendingMethodCount对应着中间类中的Methods与count(在_pk_extension_load函数中已区分了实例方法与类方法)。

然而,到目前为止只是完成了将中间类实现的protocol方法存储到struct中,并没有将实现方法注入到目标类。

__attribute__((constructor)) static void _pk_extension_inject_entry(void) {
    
    pthread_mutex_lock(&protocolsLoadingLock);

    unsigned classCount = 0;
    Class *allClasses = objc_copyClassList(&classCount);
    
    @autoreleasepool {
        for (unsigned protocolIndex = 0; protocolIndex < extendedProtcolCount; ++protocolIndex) {
            PKExtendedProtocol extendedProtcol = allExtendedProtocols[protocolIndex];
            for (unsigned classIndex = 0; classIndex < classCount; ++classIndex) {
                Class class = allClasses[classIndex];
                if (!class_conformsToProtocol(class, extendedProtcol.protocol)) {
                    continue;
                }
                _pk_extension_inject_class(class, extendedProtcol);
            }
        }
    }
    pthread_mutex_unlock(&protocolsLoadingLock);
    
    free(allClasses);
    free(allExtendedProtocols);
    extendedProtcolCount = 0, extendedProtcolCapacity = 0;
}

获取class列表后判断每个class是否遵守struct中存储的protocol协议(这里有两层for循环,要命),如果遵守协议,调用_pk_extension_inject_class函数,继续跟进:

static void _pk_extension_inject_class(Class targetClass, PKExtendedProtocol extendedProtocol) {
    
//    动态注入struct中存储的实例方法
    for (unsigned methodIndex = 0; methodIndex < extendedProtocol.instanceMethodCount; ++methodIndex) {
        Method method = extendedProtocol.instanceMethods[methodIndex];
        SEL selector = method_getName(method);
        
        if (class_getInstanceMethod(targetClass, selector)) {
            continue;
        }
        
        IMP imp = method_getImplementation(method);
        const char *types = method_getTypeEncoding(method);
        class_addMethod(targetClass, selector, imp, types);
    }

//    动态注入struct中存储的类方法
    Class targetMetaClass = object_getClass(targetClass);
    for (unsigned methodIndex = 0; methodIndex < extendedProtocol.classMethodCount; ++methodIndex) {
        Method method = extendedProtocol.classMethods[methodIndex];
        SEL selector = method_getName(method);
        
        if (selector == @selector(load) || selector == @selector(initialize)) {
            continue;
        }
        if (class_getInstanceMethod(targetMetaClass, selector)) {
            continue;
        }
        
        IMP imp = method_getImplementation(method);
        const char *types = method_getTypeEncoding(method);
        class_addMethod(targetMetaClass, selector, imp, types);
    }
}

逻辑也很简答,通过runtime动态对目标类注入struct中存储的实例方法和类方法(实例方法存储在类中,类方法存储在元类中)。

Pull requests中也发现了这样一条信息

性能问题

这里的问题描述不是很精准,并非所有的class都会在premain阶段执行initialized,而是遵守了通过@defs拓展协议的class会在premain阶段执行initialized。

作者移除了__attribute__((constructor))_pk_extension_inject_entry函数的修饰,并新增:

__attribute__((constructor)) static void _pk_extension_inject_entry(void) {
    _pk_swizzleMethod(object_getClass([NSObject class]), @selector(resolveInstanceMethod:), @selector(_pk_resolveInstanceMethod:));
    _pk_swizzleMethod(object_getClass([NSObject class]), @selector(resolveClassMethod:), @selector(_pk_resolveClassMethod:));
}
@implementation NSObject (PKExtendedProtocol)
+ (BOOL)_pk_resolveInstanceMethod:(SEL)sel {
    _pk_extension_try_inject_entry_class(self);
    return [self _pk_resolveInstanceMethod:sel];
}
+ (BOOL)_pk_resolveClassMethod:(SEL)sel {
    _pk_extension_try_inject_entry_class(self);
    return [self _pk_resolveClassMethod:sel];
}
@end
static void _pk_extension_try_inject_entry_class(Class class) {
    // 防止递归死锁,因为 class_getInstanceMethod(), 会触发 resolveInstanceMethod: 等方法的调用,就会导致递归调用,引起死锁
    // 这边没必要用 递归锁,内部 for 循环,才引起的递归。 代码不用重复执行
    NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary;
    if ([threadDictionary objectForKey:@"_pk_injecting"]) {
        return;
    }
    pthread_mutex_lock(&protocolsLoadingLock);
    [threadDictionary setObject:@1 forKey:@"_pk_injecting"];
    _pk_extension_inject_entry_class(class);
    [threadDictionary removeObjectForKey:@"_pk_injecting"];
    pthread_mutex_unlock(&protocolsLoadingLock);
}

在premain阶段hookresolveInstanceMethodresolveClassMethod,调用某个类的协议方法时,因找不到具体的方法实现会走runtime消息转发流程,此时在转发过程中注入方法实现,从而延迟目标类的initialized调用到正常逻辑。显然,it works!

libextobjc

大致看了下EXTConcreteProtocol.m中的实现,注入逻辑没什么变化,但是ProtocolKit使用起来确实更便捷。


refs:

  1. ProtocolKit
  2. 那些被遗漏的Objective-C保留字
  3. ProtocolKit 解析与改进
  4. libextobjc
上一篇下一篇

猜你喜欢

热点阅读