一、背景
现在
Objective-C
在Apple那儿现已是放养的孩子了,除了每年的修修补补,现已不再做大的改动,而Swift变成了亲儿子,每年一个大版别的更新,特别是Swift3.0
版别之后,Swift现已趋于稳定,运用的用户已超过了Ojective-C,所以关于iOS开发者来说,把握Swift开发变成了必备的技能。
关于公司新项目来说能够直接上纯Swift项目,但关于一些老项目,留给开发者的就只有运用Swift
重构
和混编
两条路了,本文就针对混编要点讲解下一些实用的tips,以便在进行混编时分更好的运用。注
(这儿只讲详细的小技巧,关于根底的混编环境网上许多,能够自己查找,这儿不做展开)。
二、常用混编tips
1、运用 @objc
润饰
假如Swift类里边的某个成员变量或许方噶想要露出给Objective-C调用,需求在前面加上 @objc
@objc let name: String
@objc func eat() {
print('aaa')
}
2、运用 @objcMembers
润饰类
运用Tip1办法,假如遇到多个成员变量和办法都需求露出,每个都加@objc显得太冗余,这时分能够运用 @objcMembers
润饰这个类,这样默许所有成员都会露出给OC(包含扩展中界说的成员)
终究是否成功露出,还需求考虑成员本身的访问等级(private、fileprivate不会露出)
@objcMembers class Car: NSObject {
var price: Double
var band: String
init(price: Double, band: String) {
self.price = price
self.band = band
}
func run() { print(price, band, "run") }
static func run() { print("Car run")
}
}
extension Car {
func test() { print(price, band, "test") }
}
3、经过 @objc
重命名Swift露出给OC的类名、特点名、函数名等
由于Objective-C没有命名空间,所以类名一般都会加上前缀,而Swift则不需求,为了符合OC的运用习惯,能够将Swift的类重命名后露出给OC进行混编调用,这样运用起来就很nice了。
@objc(EHICar)
@objcMembers class Car: NSObject {
var price: Double
@objc(name)
var band: String
init(price: Double, band: String) {
self.price = price
self.band = band
}
@objc(drive)
func run() { print(price, band, "run") }
static func run() { print("Car run") }
}
extension Car {
@objc(newTest)
func test() { print(price, band, "test") }
}
重命名后在OC中的调用如下:
EHICar *car = [[EHICar alloc] initWithPrice:30 band:@"BMW"];
car.name = @"525LI";
[car drive];
[EHICar run];
4、挑选器
在Swift里边也能够运用挑选器
,可是对应地办法有必要运用 @objc
润饰或许当时类被 @objcMembers
润饰才干运用。
@objcMembers class Car: NSObject {
func textSelector(str: String) {
print(str)
}
func run() {
perform(#selector(textSelector(str:)))
}
}
5、String与NSString
运用过Swift的应该都知道Swift在3.0版别对String进行了大改,API规划上和NSString有了很大的不同,如前缀、后缀、索引、Substring等:
var str = "123456"
func textPrint() {
print(str.hasPrefix("123")) // true
print(str.hasSuffix("456")) // true
print(str.prefix(3)) // 从最初截取三位,成果为:123
print(str.suffix(3)) // 从结尾截取三位,成果为:456
}
var str = "1_2"
func textStr() {
// 刺进 单个字符,成果是:1_2_
str.insert("_", at: str.endIndex)
// 刺进 字符串,成果是:1_2_3_4
str.insert(contentsOf: "3_4", at: str.endIndex)
// 在某个索引后边刺进,成果是:1666_2_3_4
str.insert(contentsOf: "666", at: str.index(after: str.startIndex))
// 在某个索引后边刺进,成果是:1666_2_3_8884
str.insert(contentsOf: "888", at: str.index(before: str.endIndex))
// 在某个索引后边刺进,偏移索引,成果是:1666hello_2_3_8884
str.insert(contentsOf: "hello", at: str.index(str.startIndex, offsetBy: 4))
// 删去值为1的第一个索引的值,,成果是:666hello_2_3_8884
str.remove(at: str.firstIndex(of: "1")!)
// 删去值为字符为 6 的字符,成果是:hello_2_3_8884
str.removeAll { $0 == "6" }
//删去某个区间的字符
var range = str.index(str.endIndex, offsetBy: -4)..<str.index(before: str.endIndex)
// hello_2_3_4
str.removeSubrange(range)
}
所以在混编的时分运用起来就很不方便了,这时分能够考虑将String转换为NSString运用。
6、协议
protocol
对咱们来说都很了解了,可是OC中的协议对开发者有一个痛点便是,OC的协议严格来说只能说是接口,由于不能对协议中界说的办法进行默许的完成,详细的完成还需求依靠完成类,这样在运用时分就有很大的局限性。而Swift里边的协议相对来说就很强壮了,能够在 extension
中提供默许完成。所以在混编的时分能够运用Swift来界说协议(需求@objc润饰才干够在OC中运用),然后在OC和Swift中进行运用,这样就很棒了。且假如是不用完成的函数,函数前要加上 @objc optional
。
@objc protocol CarProtocol {
func run()
}
extension CarProtocol {
func run() {
print("Car run")
}
}
7、runtime
OC的东西在Swift里边调用,会调用了 runtime
那套机制;而Swift的东西在OC里边调用,咱们打断点看汇编能够发现调用的也是runtime那套机制,而关于swift里边自己的办法走的肯定是Swift的流程,假如咱们强行让它走OC那套runtime机制,能够在 run() 函数前加 dynamic。
class Car: NSObject {
@objc dynamic func run() {
printf("Car run")
}
}
8、swift中运用KVO
Swift 要运用 KVO ,有必要满意以下条件:
-
特点所在的类、监听器终究继承自 NSObject
-
用 @objc dynamic 润饰对应的特点
import Foundation
class Acount:NSObject {
dynamic var balance:Double = 0.0
}
class Person:NSObject {
var name:String
var account:Acount?{
didSet{
if account != nil {
account!.addObserver(self, forKeyPath: "balance", options: .Old, context: nil);
}
}
}
init(name:String){
self.name = name
super.init()
}
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer) {
if keyPath == "balance" {
var oldValue = change[NSKeyValueChangeOldKey] as! Double
var newValue = (account?.balance)!
print("oldValue=\(oldValue),newValue=\(newValue)")
}
}
}
var p = Person(name: "Kenshin Cui")
var account = Acount()
account.balance = 10000000.0
p.account = account
p.account!.balance = 999999999.9 //成果:oldValue=10000000.0,newValue=999999999.9
9、枚举
OC假如想要调Swift中的枚举值时,Swift的枚举需求运用 @objc
进行润饰,然后OC就能够运用,需求留意的是,假如需求在OC中进行该枚举值的调用,书写规矩为枚举名+case的值。
注
: Swift的枚举比OC强壮的许多,所以在混编时,需求界说为Int类型后,才干供OC调用。
@objc enum CarType: Int {
case baoma = 0
case benchi
}
OC调用该枚举值时,能够直接运用 CarType
这个枚举,需求运用详细值时如 baoma
这个值,能够直接运用 CarTypeBaoma
,这个是swift编译器编译后的值,OC能够运用。
10、结构体
在oc中是不能调用struct
里边的内容的,你想在类似class前面加个 @objc
的办法加在struct
前面是不行的,那可是咱们又想在oc中调用struct的特点,那怎么办呢?咱们只能够再建一个Swift
的类,在类里写个办法来回来struct中的值
Swift代码如下:
struct CarStruct {
var name: String?
var price: Int?
init(name: String, price: Int) {
self.name = name
self.price = price
}
}
@objcMembers class CarClass: NSObject {
var car = CarStruct(name: "BMW", price: 30)
func getCarName() -> String {
return car.name ?? ""
}
func getCarPrice() -> Int {
return car.price ?? 0
}
}
在OC中调用结构领会提示找不到,所以能够运用
CarClass
这个类来直接的运用CarStruct
这个结构体。
@interface ViewController ()
//@property(nonatomic, strong) CarStruct car;
@property(nonatomic, strong) CarClass* car;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (NSString *)getCarName {
return [self.car getCarName];
}
11、OC的block与Swift的闭包
在混编中,OC中的block在Swift中能够正常运用,Swift的闭包在OC中也是能够正常运用的,测试代码如下,能够看下:
- OC类:
@interface ViewController : UIViewController
@property (nonatomic, strong) void (^myblock) (NSString *name);
@property(nonatomic, strong) SwiftText *swiftVc;
@end
// 测试swift闭包
- (void)textSwiftClosures {
self.swiftVc = [[SwiftText alloc] init];
self.swiftVc.textClosures = ^{
printf("aaaaa");
};
}
- Swift类
@objcMembers class SwiftText: NSObject {
// OC类
var ocViewController: ViewController?
// 测试闭包
var textClosures = {}
override init() {
super.init()
}
func textOcBlock() {
self.ocViewController = ViewController()
self.ocViewController?.myblock = { name in
print(name ?? "")
}
}
}
12、OC中的宏
Swift
中是不能运用OC中的宏界说
语法,Swift是有命名空间的,所以咱们能够将原本OC中不需求承受参数的宏,界说成 let常量
或枚举
,将需求承受参数的宏界说成函数
。
- 如oc的宏:
#define kScreenHeight [UIScreen mainScreen].bounds.size.height
#define kScreenWidth [UIScreen mainScreen].bounds.size.width
- 在swift中界说为大局常量:
let kScreenHeight = UIScreen.main.bounds.height
let kScreenWidth = UIScreen.main.bounds.width
13、元组
元组
是Swift特有的,在OC中是没有的,OC调用不了Swift中的元组,所以在Swift中关于OC可能用到的办法中,回来值和参数都不能是元组,Swift中OC可能用到的特点变量也不能是元组。
15、高阶函数
Swift 中界说的高阶函数(比方filter
、map
、redux
等),OC是不能调用的。
三、API混编适配
3.1、可选类型
3.1.1、关键字nonnull、nullable
Objective-C
指针既能够是一个有效值,也能够是空值,例如 null 或许 nil,这与Swift
里的可选值
行为十分相似。
假如咱们再仔细想一下,就会发现在 Objective-C 里边,每个指针类型实际上都是可选类型,每个非指针类型都对错可选类型。可是大部分时刻,一个特点或许办法不会处理输入值是 nil 的情况,或许永远不会回来 nil。
所以,默许情况下 Swift 会把 Objective-C 里的指针当做隐式解析可选类型
,由于它认为这个值大部分情况下不会是 nil,但它也不完全确定。
虽说这种转换规矩没什么毛病,但大量的隐式解析可选类型让代码变得目的模糊,好在咱们有两个关键字注解能够去描述这个目的,他们分别是 nonnull
和 nullable
。这两个注解在 Objective-C 里边仅仅用于记录开发者的目的,不是强制的。但 Swift 会用到这些信息来决定是否转换为可选类型。
3.1.2、宏 NS_ASSUME_NONNULL_BEGIN、NS_ASSUME_NONNULL_END
除了
nonnull
和nullable
以外,还有一对配合运用的宏NS_ASSUME_NONNULL_BEGIN
和NS_ASSUME_NONNULL_END
能够让咱们的代码更简练。
在这两个宏包裹的代码片段中,特点
,⽅法参数
和回来值的默许注解
都是 nonnull 类型的,这样一来,咱们就能够删掉许多冗余的代码。
3.1.3、底层关键字
可是上面的关键字和宏并不适用所有的场合,例如你将 nonnull 直接放在常量前会触发编译器过错。还好这种过错是有解决办法的!
nonull
和 nullable
只能在办法和特点上运用,假如想拓宽其运用场景,就需求直接调用这两关键字底层的内容,也便是 _Nonnull
和 _Nullable
。
这两种注解除了能够用在大局常量,大局函数的场景外,也适用于任何 Objective-C 任何地方的指针类型,乃至那种指向指针类型的指针。
3.2、Int类型
大多数人运用 NSUInteger
是为了表明这个数值是⾮负的,虽然这种用法是可行的,但它仍是会存在一些严重的安全漏洞(NSUInteger 的大小
会因架构不同而产生一些改变),所以这种规划思路并没有被 Swift 选用。
Swift
采取的战略是在进⾏有符号运算时,要求开发者有必要将⽆符号类型
转换为有符号类型
,假如 Swift 在处理⽆符号运算时,产⽣了负值,就会直接停⽌运算。
也正是这样的战略,会让 Swift
中的 Int
和 UInt
在混合起来运用的时分变得很费事,当然,这在 Objective-C
⾥⾯的也是一个棘手的问题。
所以混合运用 Int
和 UInt
并不是 Swift
里的最佳实践,在 Swift 里边,咱们建议将所有进行数值计算的类型声明为 Int
,即使它永远不行能为负数。
关于 Apple
自己的结构,他们设置了一个白名单用于将 NSUInteger 转换为 Int。
关于开发者而言,决定权在咱们自己手里,咱们能够⾃⾏挑选是否使⽤ NSInteger,但 Apple 的工程师强烈推荐你这么做。
或许在 Objective-C ⾥⾯距离不是很⼤,但在 Swift ⾥⾯很重要!
3.3、对Swift躲藏某个API
在做一个公共库时,可能会面临一个问题:其间的某个办法不期望Swift
运用,这时分只需求在原有的头⽂件⾥将相应的 Objective-C
的⽅法符号为NS_REFINED_FOR_SWIFT
即可。
例如:
- (instancetype)initWithNameComponent:(nullable NSString *)name NS_REFINED_FOR_SWIFT;
这样在Swift调用的时分,编译器会将该办法躲藏起来,比方代码补全的时分。其实这样不代表就不能调用了,这个符号做的作业其实很简单,是在对应地Swift版别的API最初增加了两个下划线
,所以假如非要运用,也能够经过调用__+办法
调用。
3.4、对Swift重命名办法名
Swift 和 Objectiv-C 的命名风格是有所不同,为了解决 API 风格上的问题,Swift 会依据一些规矩重命名,通常这个成果还不错,但这毕竟是计算机的审美成果,很难满意开发者的诉求,所以针对一些不满意的地方,咱们能够自己运用NS_SWIFT_NAME
来进行命名OC办法对应地Swift中API的办法名。
OC:
- (BOOL)driveCarByHand:(Int)handType
NS_SWIFT_NAME(driveCar(handType:));
重命名后的供Swift调用的API:
func driveCar(handType: Int) -> bool
四、总结
写到这儿,根本现已总结了项目中常见的在混编过程中会遇到的问题,从常用的特点、办法、类等到结构的API规划,当然本篇文章主要写的是在混编时分适配的Tips,所以要点写的是编译器没有帮咱们做好的作业,其实在混编中,编译器大部分帮助咱们做的仍是比较友好的,在大部分功能上能够做到OC和Swift的无缝衔接调用。