用Swift处理动态类型的方法和对象
在Objective-C中,id类型表示任何Objective-C类的实例对象。相反,Swift将id类型导入为任意类型。当您将一个Swift实例传递给Objective-C API时,它将作为id参数桥接,以便在API中作为Objective-C对象使用。当id值作为任意值导入到Swift时,运行时将自动处理到类引用或值类型的桥接。
向下转换对象以调用方法和访问属性
当您使用任何您知道底层类型的类型对象时,通常将这些对象向下转换为底层类型是非常有用的。但是,因为任何类型都可以引用任何类型,所以编译器不能保证将向下转换为更特定的类型。
您可以使用条件类型转换操作符(as?),它返回一个可选的值,该值是您尝试向下转换到的类型的可选值:
let userDefaults = UserDefaults.standard
let lastRefreshDate = userDefaults.object(forKey: "LastRefreshDate") // lastRefreshDate is of type Any?
if let date = lastRefreshDate as? Date {
print("\(date.timeIntervalSinceReferenceDate)")
}
如果您完全确定对象的类型,那么可以使用强制向下转换操作符(as!)
let myDate = lastRefreshDate as! Date
let timeInterval = myDate.timeIntervalSinceReferenceDate
概述
一些类似Objective-C api的目标-操作-接受方法或属性名作为参数,然后使用这些名称动态调用或访问方法或属性。在Swift中,使用#selector和#keyPath表达式分别将这些方法或属性名表示为选择器或密钥路径。
使用选择器来安排对Objective-C方法的调用
在Objective-C中,选择器是引用Objective-C方法名称的类型。在Swift中,Objective-C选择器由选择器结构表示,使用# Selector表达式创建它们。
在Swift中,通过将方法的名称放在#selector表达式:#selector(MyViewController.tappedButton(_:))中,可以为Objective-C方法创建选择器。要构造属性的Objective-C getter或setter方法的选择器,使用getter:或setter: label作为属性名的前缀,比如#selector(getter: MyViewController.myButton)。下面的示例显示了作为目标-操作模式一部分使用的选择器,用于调用响应touchUpInside事件的方法。
import UIKit
class MyViewController: UIViewController {
let myButton = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
let action = #selector(MyViewController.tappedButton)
myButton.addTarget(self, action: action, forControlEvents: .touchUpInside)
}
@objc func tappedButton(_ sender: UIButton?) {
print("tapped button")
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}
如果需要消除重载函数之间的歧义,请使用括号表达式和as运算符使#选择器表达式明确地指向特定的重载。
使用关键路径动态访问Objective-C属性
在Objective-C中,键是标识对象特定属性的字符串。键路径是一个点分隔键字符串,它指定要遍历的对象属性序列。键和键路径经常用于键值编码(KVC),这是一种使用字符串标识符间接访问对象属性和关系的机制。
使用#keyPath字符串表达式创建编译器选中的键和键路径,KVC方法可以使用这些键和路径,比如value(forKey:)和value(forKeyPath:)。#keyPath字符串表达式接受链式方法或属性引用。它还支持通过链中的可选值链接,比如#keyPath(Person.bestFriend.name)。使用#keyPath字符串表达式创建的键路径不会传递有关它们引用的属性或方法的类型信息,这些属性或方法引用接受键路径的api。
下面的示例定义了一个Person类,创建了它的两个实例,并使用几个#keyPath字符串表达式来访问这些属性的属性:
class Person: NSObject {
@objc var name: String
@objc var friends: [Person] = []
@objc var bestFriend: Person? = nil
init(name: String) {
self.name = name
}
}
let gabrielle = Person(name: "Gabrielle")
let jim = Person(name: "Jim")
let yuanyuan = Person(name: "Yuanyuan")
gabrielle.friends = [jim, yuanyuan]
gabrielle.bestFriend = yuanyuan
#keyPath(Person.name)
// "name"
gabrielle.value(forKey: #keyPath(Person.name))
// "Gabrielle"
#keyPath(Person.bestFriend.name)
// "bestFriend.name"
gabrielle.value(forKeyPath: #keyPath(Person.bestFriend.name))
// "Yuanyuan"
#keyPath(Person.friends.name)
// "friends.name"
gabrielle.value(forKeyPath: #keyPath(Person.friends.name))
// ["Yuanyuan", "Jim"]
将Objective-C导入Swift
使用Swift访问Objective-C代码中的类和其他声明。
您可以在单个项目中同时使用Objective-C和Swift文件,无论项目最初使用哪种语言。这使得创建混合语言应用程序和框架目标就像创建用单一语言编写的应用程序或框架目标一样简单。
在混合语言目标中使用Swift代码中的Objective-C声明的过程略有不同,这取决于您是在编写应用程序还是框架。这两个过程描述如下。
导入应用程序目标中的代码
要将一组Objective-C文件导入到同一个app target中的Swift代码中,需要依赖Objective-C桥接头文件将这些文件暴露给Swift。当您将Swift文件添加到现有的Objective-C应用程序或将Objective-C文件添加到现有的Swift应用程序时,Xcode提供了创建这个头文件的功能。
如果接受,Xcode将创建桥接头文件和正在创建的文件,并使用产品模块名后面跟着“-Bridging-Header.h”来命名它们。或者,您可以通过选择 File > New > File > [operating system] > Source > Header File.,自己创建一个桥接头文件。
编辑桥接头,将Objective-C代码暴露给Swift代码:
在Objective-C桥接头中,导入你想要公开给Swift的每个Objective-C头。
在构建设置中,在Swift编译器-代码生成中,确保Objective-C桥接头构建设置有到桥接头文件的路径。路径应该与项目相关,类似于信息的方式。plist路径在构建设置中指定。在大多数情况下,您不需要修改这个设置。
在框架目标中导入代码
要在与Swift代码相同的框架目标文件中使用Objective-C声明,您需要将这些文件导入Objective-C伞形标题——框架的主标题。通过配置雨伞头导入Objective-C文件:
在构建设置中,在打包中,确保将框架目标的定义模块设置设置为Yes。
在伞形标头中,导入要公开给Swift的每个Objective-C标头。
Swift看到你在伞头中公开的每个标题。该框架中的Objective-C文件的内容可以从该框架目标中的任何Swift文件中自动获得,不需要导入语句。使用Objective-C代码中的类和其他声明,使用与系统类相同的Swift语法。
将Swift导入Objective-C
从Objective-C代码库中访问Swift类型和声明。
概述
通过导入xcode生成的头文件,您可以在项目中的Objective-C代码中使用Swift声明的类型。这个文件是Objective-C的头文件,它声明目标中的Swift接口,您可以将它看作是Swift代码的伞形头文件。您不需要做任何特殊的事情来创建生成的header—只需导入它来在Objective-C代码中使用它的内容。
标题的名称是由您的产品模块名称生成的,后面跟着“-Swift.h”。默认情况下,此名称与产品名称相同,任何非字母数字字符都用下划线(_)替换。如果名称以数字开头,则将第一个数字替换为下划线。
将Swift声明导入Objective-C代码的过程略有不同,这取决于您是在编写应用程序还是框架。这两个过程描述如下。
导入应用程序目标中的代码
当您构建应用程序目标时,您可以使用这种语法将您的Swift代码导入到同一目标内的任何Objective-C .m文件中,并替换适当的名称:
#import"ProductModuleName-Swift.h"
默认情况下,生成的头文件包含使用public或open修饰符标记的Swift声明的接口。如果您的应用程序目标有一个Objective-C桥接头,生成的头还包括用内部修饰符标记的接口。标记为private或fileprivate修饰符的声明不会出现在生成的头文件中,也不会公开给Objective-C运行时,除非它们显式地标记为@IBAction、@IBOutlet或@objc属性。在单元测试目标内部,通过将@testable放在product module import语句前面,您可以访问导入的内部声明,就好像它们是公共的一样。
在框架目标中导入代码
要在与Objective-C代码相同的框架目标中导入一组Swift文件,请将您的Swift代码的xcode生成的头文件导入到您想要使用Swift代码的任何Objective-C .m文件中。
因为生成的标头是框架的公共接口的一部分,所以在为框架目标生成的标头中只会出现标记为public或open修饰符的声明。用内部修饰符标记并在从Objective-C类继承的类中声明的方法和属性可以被Objective-C运行时访问。但是,它们在编译时不可访问,并且不会出现在框架目标的生成头文件中。
在相同的框架下,将Swift代码导入Objective-C:
在构建设置中,在打包中,确保为该框架目标定义的模块设置设置为Yes。
使用这种语法并替换适当的名称,将Swift代码从该框架目标导入到该目标中的任何Objective-C .m文件中:
#import <ProductName/ProductModuleName-Swift.h>
使用前向声明在Objective-C标头中包含Swift类
当Objective-C头文件中的声明引用来自同一目标的Swift类或协议时,导入生成的头将创建循环引用。为了避免这种情况,使用Swift类或协议的前向声明在Objective-C接口中引用它。
// MyObjcClass.h@classMySwiftClass;
@protocolMySwiftProtocol;
@interfaceMyObjcClass : NSObject
(MySwiftClass *)returnSwiftClassInstance;
- (id)returnInstanceAdoptingSwiftProtocol;
// ...@end
Swift类和协议的前向声明只能用作方法和属性声明的类型。