iOS混编相关知识点

前言

iOS混编也是老生常谈的问题,本篇文章我们将从实际开发中不同使用场景来探究混编的相关知识点,以便在实际项目中混编相关报错问题的处理。

  • 主工程内混编
  • 主工程与依赖库混编
  • 依赖库内部混编
主工程内混编

主工程内无外乎是Swift工程调用OC文件和OC工程调用Swift文件。
主工程为OC时,在第一次生成Swift文件时候,系统会弹窗提示是否创建桥接文件ProjectName-Bridging-Header.h。
同样的主工程为Swift时,在第一次生成OC文件时,也会弹窗询问是否创建桥接文件。



桥接文件是为了将需要在Swift文件中使用到的OC文件的头文件引入,编译器则会将引入的所有oc文件进行module化,即在swift的角度而言,引入的每个oc文件即为引入的每个module,这样做的目的也主要是为了提高编译速度,在编译的过程中,编译过的module会放入一个编译好的modules数据结构中,当有重复的module引入时,dyld会将先前编译好的module进行link产生最终的mach-o文件。

而如果是OC文件中使用Swift文件时,则需要在使用的OC文件中引入ProjectName-Swift.h文件,可在Targets -> Build Settings中查看。



该文件会在编译的过程中产生,其主要的目的是为了将Swift转译为OC,例如将一个类暴露给OC的信息展示出来,这样在OC文件中就能直接使用了。
但由于Swift是静态语言,所以要想OC文件能访问到Swift的信息,例如一个类的信息,则需要满足以下若干条件:

  1. 类继承于NSObject基类。
  2. 需要访问的属性方法需要在继承于基类基础之上再加上 @objc 修饰。
  3. 如果想要使用方法交换,那么在以上基础上还需要加上 dynamic 修饰(@objc 在前)。
依赖库内部混编
.modulemap文件

在正式探索这一块知识之前,我们先来了解下.modulemap文件,查看项目编译后的AFNetworking.framework文件夹,标准的.framework依赖库格式如下所示:



所有头文件存放在Headers目录下,点击module.modulemap文件:

framework module AFNetworking {
  umbrella header "AFNetworking-umbrella.h"

  export *
  module * { export * }
}

在此module文件中所写代码意思就是将AFNetworking.framework中的头文件暴露给外界,通过AFNetworking-umbrella.h这个文件,查看此文件如下:

#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif

#import "AFHTTPSessionManager.h"
#import "AFURLSessionManager.h"
#import "AFCompatibilityMacros.h"
#import "AFNetworkReachabilityManager.h"
#import "AFSecurityPolicy.h"
#import "AFURLRequestSerialization.h"
#import "AFURLResponseSerialization.h"

FOUNDATION_EXPORT double AFNetworkingVersionNumber;
FOUNDATION_EXPORT const unsigned char AFNetworkingVersionString[];

类似于上方的桥接文件,AFNetworking-umbrella.h也是给外界暴露依赖库的头文件。
framework module AFNetworking:标准写法,将AFNetworking设为一个名为AFNetworking的模块。
umbrella header "AFNetworking-umbrella.h":设置此模块的伞文件,伞文件中引入了库中的头文件。
export *:将AFNetworking库中本来依赖的其余东西带出去。
module * { export * }:如果存在子模块时候,继续将子模块及子模块依赖的所有东西一起带出去。

自定义.framework

例如上方工程中,需要使用LcrTiger类,常规引入头文件方法为#import "LcrTiger.h"、#import "Head/LcrTiger.h"、绝对路径#import "/Users/lichuanrong/Desktop/LcrApp/LcrApp/Head/LcrTiger.h"三种方式。
但是我们利用.modulemap文件将LcrTiger包装成一个模块,然后利用@import Tiger; 来引入Tiger类。
Xcode配置文件LcrApp-Debug.xcconfig中给项目增加module文件路径如下:

OTHER_CFLAGS = "-fmodule-map-file=${SRCROOT}/LcrApp/Head/TigerModule.modulemap"

TigerModule.modulemap代码如下:

module Tiger {
    header "LcrTiger.h"
    export *
}

如果将export *注释掉,则LcrTuger.m中的外部符号NSLog就会报错:



所以export *的作用也就不用再解释了吧。

接下来我们手动将Head文件夹封装成.framework静态库,仿照上方AFNetworking.framework标准样式。


  1. 将Head文件夹更改为Head.framework。
  2. 新建Headers文件夹存放头文件LcrTiger.h。
  3. 修改TigerModule文件,添加framework 前缀。
framework module Tiger {
    header "LcrTiger.h"
    export *
}
  1. 修改config文件中TigerModule文件路径。

同样利用@import Tiger;即可引入此模块。

子模块及伞文件
新建LcrDog类,将LcrDog.h拖进Headers文件夹内,新建Animal-umbrella.h伞文件。


在Animal-umbrella.h文件中分别引入LcrTiger.h、LcrDog.h两个头文件,更改TigerModule如下:

framework module Animal {
    umbrella header "Animal-umbrella.h"
    export *
}

通过添加 umbrella 修饰符,将Animal-umbrella.h文件作为模块Animal的伞文件,其中以引入了外界需要使用的类的头文件,所以外界使用@import Animal;引入整个模块即可。
这样一个简单的.framework就制作好了。
优化
如果一个库文件内也分几个小模块,那么就需要创建子Module,例如将上方的LcrTiger、LcrDog分别作为小模块,如下所示:

framework module Animal {
    umbrella header "Animal-umbrella.h"
    export *
    
    module LcrTiger {
        header "LcrTiger.h"
        export *
    }
    
    module LcrDog {
        header "LcrDog.h"
        export *
    }
}

此时@import Animal;引入依然是没问题的,注释掉Animal-umbrella.h文件中的引入都是没啥问题的。也可分别引入子模块:

@import Animal.LcrTiger;
@import Animal.LcrDog;
  1. 当需要显式地引入相应子模块时,就需要在相应子模块前添加explicit 修饰符,注释掉Animal-umbrella.h文件中的引入,如果还是利用@import Animal;的话,那么就会报错,解决方法就是@import Animal.LcrTiger;显式地引入。
  2. 如果子Module中不制定头文件,那么就不能注释掉Animal-umbrella.h文件中的引入,因为这样写的话,系统就会默认将伞文件中的头文件看作是一个个子Module。
framework module Animal {
    umbrella header "Animal-umbrella.h"
    export *
    
    module * {
        export *
    }
}
//Animal-umbrella.h
#import <Head/LcrTiger.h>
#import <Head/LcrDog.h>

注:(使用子模块的好处就是如果库中头文件太多,可以将部分用到的文件放入子模块,显式引入子模块即可,提高代码效率,例如库中有100个头文件,80个放入伞文件中,而我需要使用另外20个,那么就可以将另外20个放入子模块中,那80个我不用就不引入,就不参与编译,大大节省时间)

子模块间关系
子模块间如果有引用关系,例如LcrTiger中使用到LcrDog,可利用@import LcrDog;先将LcrDog子模块引入,然后export LcrDog发射出去,外界LcrDog也可用。

framework module Animal {
    umbrella header "Animal-umbrella.h"
    export *
}

module LcrDog {
    header "Headers/LcrDog.h"
    export *
}

module LcrTiger {
    header "Headers/LcrTiger.h"
    export LcrDog
    export *
}

拓展--requires

framework module Animal {
    umbrella header "Animal-umbrella.h"
    export *
}

//点语法 默认是Animal的子module
//requires objc:使用Animal.Swift 模块的源文件必须是OC文件。
module Animal.Swift {
    header "Headers/LcrTiger.h"
    requires objc
}

创建SwiftFrameW.framework项目,项目初始语言为Swift,新建Teacher.swift文件,以及OC类Lcr:



注意:在上文主工程内混编中,系统会弹窗提示是否需要桥接文件,但在这个项目目前的过程中没有,也就是说没有桥接文件,swift文件如何使用oc信息。
那么如上所示的想要在Teacher.swift文件中使用Lcr类,则需要将引入到SwiftFrameW.h头文件中,这个文件时framwork创建时就自带的,作用类似上文中的伞文件,#import "Lcr.h" 发现报错如下:

Include of non-modular header inside framework module 'SwiftFrameW': '/Users/lichuanrong/Desktop/SwiftFrameW/SwiftFrameW/Objc/Lcr.h'

此时需要将Lcr.h头文件移到public中,以便对外暴露,在public中的头文件最终编译后存放在Headers文件夹中,外界访问库文件时就是通过访问Headers中的头文件的。



再次编译,报错消失了。

那么如果Lcr.m中使用Teacher,方法与常规主工程中方法一样,需要引入SwiftFrameW-Swift.h文件,就是写法不一样:

#import <SwiftFrameW/SwiftFrameW-Swift.h>

那如果是利用modulemap的方式呢?

  1. 新建module.modulemap文件。
  2. 新建xcconfig文件配置参数。(PROJECT里面设置好debug模式)
  3. 注释SwiftFrameW.h里对Lcr.h的引用。



    发现编译错误,因为暂时还没引用,即找不到相应的头文件。
    要不就将SwiftFrameW.h里的注释打开,要不就将modulemap文件修改如下:

framework module SwiftFrameW {
    umbrella header "SwiftFrameW.h"
    export *
    
    module Lcr {
        header "Lcr.h"
        export *
    }
    
    module * {
        export *
    }
}

这样编译就不报错了。

Public Private Project
上方是将Lcr.h头文件放在Public中,编译产物中,Lcr.h存放在Headers文件夹中,如果将它拖进Private中,编译依然没问题,编译产物中Lcr.h就被放入到PrivateHeaders文件夹中。


在Teacher中依然能够访问到Lcr类信息,但是如果将Lcr.h设为单独的一个Private的模块,如下:

利用import SwiftFrameW_Private 依然能通过编译,说明不管Lcr.h放在Public还是Private中都能被外界访问到。

主工程与依赖混编

利用上方创建的主工程和framework静态库合并成一个xcworkspace,且主工程引入SwiftFrameW.framework,探索主工程与依赖库之间的混编。



如上图所示,主工程依赖SwiftFrameW库,之间的混编如下:

  1. 主工程OC访问依赖库OC文件:#import <SwiftFrameW/Lcr.h> 或者 @import SwiftFrameW;
  2. 主工程OC访问依赖库Swift文件:#import <SwiftFrameW/SwiftFrameW-Swift.h>,注意Swift 中需要用public或open修饰,因为是跨target的。
  3. 主工程OC访问主工程Swift文件:#import "工程名-Swift.h"
  4. 主工程Swift访问依赖库OC和Swift文件:@import SwiftFrameW
  5. 主工程Swift访问主工程OC文件: 工程名-Bridging-Header.h内引入头文件即可。

更多详情访问:
Swift与OC混编
OC项目倒入Swift三方库不兼容问题
Swift调用OC

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335

推荐阅读更多精彩内容