前言
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的信息,例如一个类的信息,则需要满足以下若干条件:
- 类继承于NSObject基类。
- 需要访问的属性方法需要在继承于基类基础之上再加上 @objc 修饰。
- 如果想要使用方法交换,那么在以上基础上还需要加上 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标准样式。
- 将Head文件夹更改为Head.framework。
- 新建Headers文件夹存放头文件LcrTiger.h。
- 修改TigerModule文件,添加framework 前缀。
framework module Tiger {
header "LcrTiger.h"
export *
}
- 修改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;
- 当需要显式地引入相应子模块时,就需要在相应子模块前添加explicit 修饰符,注释掉Animal-umbrella.h文件中的引入,如果还是利用@import Animal;的话,那么就会报错,解决方法就是@import Animal.LcrTiger;显式地引入。
- 如果子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的方式呢?
- 新建module.modulemap文件。
- 新建xcconfig文件配置参数。(PROJECT里面设置好debug模式)
-
注释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库,之间的混编如下:
- 主工程OC访问依赖库OC文件:
#import <SwiftFrameW/Lcr.h>
或者@import SwiftFrameW;
- 主工程OC访问依赖库Swift文件:
#import <SwiftFrameW/SwiftFrameW-Swift.h>
,注意Swift 中需要用public或open修饰,因为是跨target的。 - 主工程OC访问主工程Swift文件:
#import "工程名-Swift.h"
- 主工程Swift访问依赖库OC和Swift文件:
@import SwiftFrameW
- 主工程Swift访问主工程OC文件:
工程名-Bridging-Header.h
内引入头文件即可。
更多详情访问:
Swift与OC混编
OC项目倒入Swift三方库不兼容问题
Swift调用OC