iOS之深入解析静态Pod与动态Pod

举报
Serendipity·y 发表于 2022/02/16 23:32:06 2022/02/16
【摘要】 一、静态库与动态库 在项目中使用 pod 实现模块化,对于子模块和第三类库的导入方式存在两种:静态库、动态库。当在 podfile 中指定 use_frameworks! 时,子模块和第三方类库将被打包...

一、静态库与动态库

  • 在项目中使用 pod 实现模块化,对于子模块和第三类库的导入方式存在两种:静态库、动态库。
  • 当在 podfile 中指定 use_frameworks! 时,子模块和第三方类库将被打包成 .framework 动态库,模块之间的代码不能直接引用,需要添加依赖;

在这里插入图片描述

  • 反之(默认情况)将打包成 .a 静态库:

在这里插入图片描述

  • 动态库和静态库的区别:
    • 资源加载方式;
    • 包的大小;
    • 编译速度。

二、资源加载方式

  • s.dependency 'xx’:
    • 静态方式中各模块的 podspec 文件不用设置依赖,就可以直接 #import 其他模块的类头文件:

在这里插入图片描述

    • 而动态方式则会报错:

在这里插入图片描述

  • s.resources:
s.resources = ['Classes/**/*.{xib,storyboard,Bundle,png,gif,jpg,jpeg,txt}', 'Resource/**/*']

  
 
  • 1
  • 图片等资源是都放入 mainbundle,直接用 imageNamed: 访问,不用增加很多获取 bundle 的代码:
s.resource = 'xx/xxx.bundle'
s.resource_bundles = { 'xxx' => ['/Classes/**/*.{xib,storyboard,Bundle,png,gif,jpg,jpeg,txt}', 'Resource/**/*'] }

  
 
  • 1
  • 2
  • 这两种写法,资源都在模块自己的 bundle 里面,文件名为 xxx.bundle,工程中需要通过 bundleForClass 等获取资源路径。

三、包的大小

  • 使用 use_frameworks! 的动态包:

在这里插入图片描述

  • 默认(或使用 use_modular_headers!)的静态包:

在这里插入图片描述

  • 多次验证,结果都是动态的更小。
  • 需要注意,不同的证书 ipa 的大小不同:
动态 pod:adhoc  58.9M,pub  130.6M
静态 pod:adhoc  58.3M,pub  131.6M

  
 
  • 1
  • 2
  • 差别这么大的原因是:工程使用 swift 的代码:

在这里插入图片描述

四、编译速度

  • 动态库 Pod 方式:
Total pre-main time: 819.46 milliseconds (100.0%)
         dylib loading time: 542.43 milliseconds (66.1%)
        rebase/binding time:  35.86 milliseconds (4.3%)
            ObjC setup time:  57.79 milliseconds (7.0%)
           initializer time: 183.27 milliseconds (22.3%)
           slowest intializers :
             libSystem.B.dylib :  10.33 milliseconds (1.2%)
    libMainThreadChecker.dylib :  28.76 milliseconds (3.5%)
                  AFNetworking :  83.46 milliseconds (10.1%)
                         Realm :  27.37 milliseconds (3.3%)
               CYKJBasicModule :  17.54 milliseconds (2.1%)

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 静态库 Pod 方式:
Total pre-main time: 591.00 milliseconds (100.0%)
         dylib loading time: 447.06 milliseconds (75.6%)
        rebase/binding time:  30.51 milliseconds (5.1%)
            ObjC setup time:  20.26 milliseconds (3.4%)
           initializer time:  93.04 milliseconds (15.7%)
           slowest intializers :
             libSystem.B.dylib :   7.67 milliseconds (1.2%)
    libMainThreadChecker.dylib :  25.41 milliseconds (4.3%)
                         Realm :  21.13 milliseconds (3.5%)
                      CYKJMain :  57.87 milliseconds (9.7%)

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

五、工程实例

  • 在项目开发中的场景是一个第三方类库 bongSDK.framework 引入了 Realm.framework 和 RealmSwift.framework,RealmSwift.framework 是通过 swift 语言写的,它的内部调用 Realm。
  • 最初静态方式的 pod 遇到了难以理解的报错,因为知识的欠缺和时间的紧迫,放弃了静态这条路,使用 use_frameworks! 动态 pod 的方式。
  • 动态方式在 pod install 阶段没有报错,但子模块需要添加依赖,更困难的是图片、xib、storyboard 等资源需要获取到模块的 bundle 才能加载,导致工程大面积的图片加载错误,页面跳转崩溃。因此不得不增加很多获取 bundle 路径的代码,修改的位置几百上千处。
 + (NSBundle *)bundleWithClassName:(Class)cls moduleName:(NSString*)module {
    if (module == nil) {
        return [NSBundle mainBundle];
    }
    
    NSBundle * bundle = [NSBundle bundleForClass:cls];
    NSURL * bundleURL = [bundle URLForResource:module withExtension:@"bundle"];
    
    if (bundleURL == nil) {

        __block UINavigationController* nav;
        [[UIApplication sharedApplication].windows enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            UIViewController * windowVC = obj.rootViewController;
            if ([windowVC isKindOfClass:[UINavigationController class]]) {
                nav = (UINavigationController *)windowVC;
                *stop = YES;
            }
        }];

        if (nav != nil) {
            Class callerCls = [nav.viewControllers.firstObject class];
            bundle = [NSBundle bundleForClass:callerCls];
            bundleURL = [bundle URLForResource:module withExtension:@"bundle"];
        }

        if (bundleURL == nil) {
            return [NSBundle mainBundle];
        }
    }
    return [NSBundle bundleWithURL:bundleURL];
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 图片加载则更加困难,因为很多图片是在 xib 中写的,通过断点发现,系统并没有调用 imageNamed: 方法,导致使用 runtime 替换方法实现图片位置修改的方式失败,通过查找资料,发现 xib 中的 UIButton、UIImageView 会调用 - initWithCoder: 方法,底层会调用 UINibDecoder 类的 decodeObjectForKey。
  • runtime 替换 decodeObjectForKey 方法后,打印输出发现,UIButton、UIImageView 控件加载的图片名称在 UIResourceName 字段。由此就有了如下的处理方式:
+ (void)load {
    _imageViewImageArray = [NSMutableArray arrayWithCapacity:2];

    propKey = [CYKJXUtil stringByReversed:@"emaNecruoseRIU"];
    btnKey = [CYKJXUtil stringByReversed:@"tnetnoClufetatSnottuBIU"];

    // hook UINibDecoder - decodeObjectForKey:
    NSString* clsName = [NSString stringWithFormat:@"redoce%@biNIU", @"D"];
    clsName = [CYKJXUtil stringByReversed:clsName];

    [HookTool exchangeInstanceMethod:NSClassFromString(clsName)
                         originalSEL:@selector(decodeObjectForKey:)
                         swizzledSEL:@selector(swizzle_decodeObjectForKey:)];

    // hook UIImageView - initWithCoder:
    [HookTool exchangeInstanceMethod:UIImageView.class
                         originalSEL:@selector(initWithCoder:)
                         swizzledSEL:@selector(swizzle_imageView_initWithCoder:)];

    // hook UIButton - initWithCoder:
    [HookTool exchangeInstanceMethod:UIButton.class
                         originalSEL:@selector(initWithCoder:)
                         swizzledSEL:@selector(swizzle_button_initWithCoder:)];
}

- (id)swizzle_decodeObjectForKey:(NSString *)key {
    Method originalMethod = class_getInstanceMethod([HookTool class], @selector(swizzle_decodeObjectForKey:));
    IMP function = method_getImplementation(originalMethod);
    id (*functionPoint)(id, SEL, id) = (id (*)(id, SEL, id)) function;
    id value = functionPoint(self, _cmd, key);
    
    // 保存图片名称
    if ([key isEqualToString:propKey]) {
        [_imageViewImageArray addObject:value];
    }
    
    // 保存 button 状态数据
    if ([key isEqualToString:btnKey]) {
        if ([value isKindOfClass:[NSDictionary class]]) {
            _buttonImageDictionary = value;
        }
    }
    return value;
}

#pragma mark - UIImageView
- (id)swizzle_imageView_initWithCoder:(NSCoder *)aDecoder {
    // 执行顺序:initWithCoder -》DecoderWithKey -》setImage:,所以每次给 imageView 设置图片时,需要将之前的置空。
    // tabbarItem 的图片设置不会执行 initWithCoder,如果不置空,会导致 imageView 设置成和 tabbarItem 一样的图片。
    [_imageViewImageArray removeAllObjects];

    UIImageView * instance = (UIImageView *)[self swizzle_imageView_initWithCoder:aDecoder];

    // 设置 image
    if (_imageViewImageArray.count > 0) {
        UIImage * normalImage = [HookTool imageAfterSearch:_imageViewImageArray[0]];
        if (normalImage) {
            instance.image = normalImage;
        }
    }
    // 设置 highlightedImage
    if (_imageViewImageArray.count > 1) {
        UIImage * highlightedImage = [HookTool imageAfterSearch:_imageViewImageArray[1]];
        if (highlightedImage) {
            instance.highlightedImage = highlightedImage;
        }
    }
    return instance;
}

#pragma mark - UIButton
 - (id)swizzle_button_initWithCoder:(NSCoder *)aDecoder {
    // 执行顺序:initWithCoder -》DecoderWithKey -》setImage:,所以每次给 button 设置图片时,需要将之前的置空。
    // tabbarItem 的图片设置不会执行 initWithCoder,如果不置空,会导致 button 设置成和 tabbarItem 一样的图片。
    [_imageViewImageArray removeAllObjects];
    _buttonImageDictionary = nil;
    
    UIButton * instance = (UIButton *)[self swizzle_button_initWithCoder:aDecoder];
    
    @autoreleasepool {
        [_buttonImageDictionary enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key,
                                                                    id  _Nonnull obj,
                                                                    BOOL * _Nonnull stop) {
            
            if (_imageViewImageArray.count == 0) {
                *stop = YES;
            }
            else {
                switch ([key integerValue]) {
                    case ButtonImageOrder_Normal:
                        [HookTool setImageForButton:instance object:obj state:UIControlStateNormal];
                        break;
                    case ButtonImageOrder_Highlighted:
                        [HookTool setImageForButton:instance object:obj state:UIControlStateHighlighted];
                        break;
                    case ButtonImageOrder_Selected:
                        [HookTool setImageForButton:instance object:obj state:UIControlStateSelected];
                        break;
                    case ButtonImageOrder_Disabled:
                        [HookTool setImageForButton:instance object:obj state:UIControlStateDisabled];
                        break;
                }
            }
        }];
    }
    return instance;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 如上可见,这种动态方式对于编码并不友好,资源必须要特定的 bundle,一旦资源路径出错,轻则图片未加载,重则程序崩溃。因此,需要研究下如果使用静态方式 pod 子模块代码。
  • 首先将 use_frameworks! 删除,重新执行 pod install,等 Pod installation complete! 之后,运行工程,报错,一个一个的解决:
    • dyld: Library not loaded: @rpath/Realm.framework/Realm,现在不用 pod 导入 realm,而是将 realm.framework 拖入 basicModule 工程。官方最新的 realm.framework,它分为静态版和动态版,添加到工程的 Embedded Binaries,编译时报错 Unknown type name namespace。不管通过修改 .h 为 .hpp,还是修改 build settings -> Compile Sources As -> Objectoive-C++ 都没有效果,无计可施之时,可以将 use_frameworks! 时,cocoapods 生成的 Realm.framework 拷贝一份,拖入工程,编译运行,这个问题得到解决。
    • Library not loaded: @rpath/libswiftCore.dylib:Build Settings 中 Aways Embed Swift Standard Libraries 修改成 YES。
    • Argument list too long: recursive header expansion failed:Search Paths -> Header Search Paths,去掉 $(PODS_ROOT)/**,去掉不必要的 recursive search。
  • 其余的就是解决一些资源加载问题,资源重名问题,动态库的引用问题 #import “” 改为 #import <>:

在这里插入图片描述

文章来源: blog.csdn.net,作者:Serendipity·y,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/Forever_wj/article/details/120853770

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。