@selector
是 Objective-C
时代的一个关键字,它能够将一个方法转换并赋值给一个 SEL
类型,它的表现很相似一个动态的函数指针。在 Objective-C 时 selector
很是经常使用,从设定target-action
,到自举询问是否响应某个方法,再到指定接受通知时须要调用的方法等等,都是由 selector
来负责的。在 Objective-C 里生成一个 selector
的方法通常是这个样子的git
-(void) callMe {
//...
}
-(void) callMeWithParam:(id)obj {
//...
}
SEL someMethod = @selector(callMe);
SEL anotherMethod = @selector(callMeWithParam:);
// 或者也可使用 NSSelectorFromString
// SEL someMethod = NSSelectorFromString(@"callMe");
// SEL anotherMethod = NSSelectorFromString(@"callMeWithParam:");
复制代码
通常为了方便,不少人会选择使用 @selector,可是若是要追求灵活的话,可能会更愿意使用 NSSelectorFromString 的版本 -- 由于咱们能够在运行时动态生成字符串,经过方法名来调用对应的方法
程序员
在 Swift 中没有 @selector
了,取而代之,从 Swift 2.2 开始咱们使用 #selector
来从暴露给 Objective-C 的代码中获取一个 selector
。相似地,在 Swift 里对应原来 SEL
的类型是一个叫作 Selector
的结构体github
@objc func callMe() {
//...
}
@objc func callMeWithParam(obj: AnyObject!) {
//...
}
let someMethod = #selector(callMe)
let anotherMethod = #selector(callMeWithParam(obj:))
复制代码
【注】selector
实际上是 Objective-C runtime
的概念。在 Swift 4 中,默认状况下全部的 Swift 方法在 Objective-C 中都是不可见的,因此你须要在这类方法前面加上 @objc
关键字,将这个方法暴露给 Objective-C,才能进行使用编程
若是方法名字在方法所在域内是惟一的话,咱们能够简单地只是用方法的名字来做为 #selector
的内容。相比于前面带有冒号的完整的形式来讲,这么写起来会方便一些swift
let someMethod = #selector(callMe)
let anotherMethod = #selector(callMeWithParam)
复制代码
若是同一个做用域里面存在一样名字的两个方法,可是参数不一样,咱们能够经过将方法强制转换来使用api
@objc func commonFunc() {}
@objc func commonFunc(input: Int) -> Int {
return input
}
let method1 = #selector(commonFunc as ()->())
let method2 = #selector(commonFunc as (Int)->Int)
复制代码
class MyClass {
func method(number: Int) -> Int {
return number + 1
}
}
复制代码
想要调用method
方法的话,最普通的使用方式是生成MyClass
的实例,而后用 .method
来调用它数组
let cls = MyClass()
cls.method(number: 1)
复制代码
咱们还能够把刚才的方法该成下面这样安全
let f = MyClass.method
let object = MyClass()
let result = f(object)(1)
复制代码
咱们观察f
类:alt+单击
bash
let f: (MyClass) -> (Int) -> Int
复制代码
其实对于 Type.instanceMethod
这样的取值语句,实际上刚才多线程
let f = MyClass.method
复制代码
作的事情相似于下面字面量的转换
let f = { (obj: MyClass) in obj.method }
复制代码
在OC中单例的公认写法
@implementation MyManager
+ (id)sharedManager {
static MyManager * staticInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
staticInstance = [[self alloc] init];
});
return staticInstance;
}
@end
复制代码
使用 GCD 中的 dispatch_once_t
能够保证里面的代码只被调用一次,以此保证单例在线程上的安全
在Swift中移出了dispatch_once
,可是咱们有更简单的写法
class MyManager {
static let shared = MyManager()
private init() {}
}
复制代码
在 C 系语言中,可使用#if
或者 #ifdef
之类的编译条件分支来控制哪些代码须要编译,而哪些代码不须要。Swift 中没有宏定义的概念,所以咱们不能使用#ifdef
的方法来检查某个符号是否通过宏定义。可是为了控制编译流程和内容,Swift 仍是为咱们提供了几种简单的机制来根据需求定制编译内容的。
首先是 #if
这一套编译标记仍是存在的,#elseif
和#else
是可选的。
#if <condition>
#elseif <condition>
#else
#endif
复制代码
可是这几个表达式里的 condition 并非任意的。Swift 内建了几种平台和架构的组合,来帮助咱们为不一样的平台编译不一样的代码,具体地
方法 | 可选参数 |
---|---|
os() | macOS, iOS, tvOS, watchOS, Linux |
arch() | x86_64, arm, arm64, i386 |
swift() | >= 某个版本 |
若是咱们统一咱们在 iOS 平台和 Mac 平台的关于颜色的 API 的话,一种可能的方法就是配合 typealias 进行条件编译:
#if os(macOS)
typealias Color = NSColor
#else
typealias Color = UIColor
#endif
#if arch(x86_64)
#else
#endif
#if swift(>=14.0)
#else
#endif
复制代码
对自定义符号进行编译
咱们须要使用同一个 target 完成同一个 app 的收费版和免费版两个版本,而且但愿在点击某个按钮时收费版本执行功能,而免费版本弹出提示的话,可使用相似下面的方法
func someButtonPressed(sender: AnyObject!) {
#if FREE_VERSION
// 弹出购买提示,导航至商店等
#else
// 实际功能
#endif
}
复制代码
在这里咱们用 FREE_VERSION
这个编译符号来表明免费版本。为了使之有效,咱们须要在项目的编译选项中进行设置,在项目的 Build Settings
中,找到 Swift Compiler - Custom Flags
,并在其中的Other Swift Flags
加上-D FREE_VERSION
就能够了。
在 C 系语言中,程序的入口都是 main 函数。对于一个 Objective-C 的 iOS app 项目,在新建项目时, Xcode 将帮咱们准备好一个 main.m 文件,其中就有这个 main 函数
int main(int argc, char * argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil,
NSStringFromClass([AppDelegate class]));
}
}
复制代码
这个方法将根据第三个参数初始化一个 UIApplication 或其子类的对象并开始接收事件 (在这个例子中传入 nil,意味使用默认的 UIApplication)。最后一个参数指定了 AppDelegate 类做为应用的委托,它被用来接收相似 didFinishLaunching 或者 didEnterBackground 这样的与应用生命周期相关的委托方法。另外,虽然这个方法标明为返回一个 int,可是其实它并不会真正返回。它会一直存在于内存中,直到用户或者系统将其强制终止
新建一个 Swift 的 iOS app 项目后,咱们会发现全部文件中都没有一个像 Objective-C 时那样的 main
文件,也不存在 main 函数
。惟一和main
有关系的是在默认的 AppDelegate 类的声明上方有一个 @UIApplicationMain
的标签。
其实 Swift 的 app 也是须要 main 函数的,只不过默认状况下是 @UIApplicationMain
帮助咱们自动生成了而已。
如咱们在删除 @UIApplicationMain 后,在项目中添加一个 main.swift
文件,而后加上这样的代码
UIApplicationMain(Process.argc, Process.unsafeArgv, nil,
NSStringFromClass(AppDelegate))
复制代码
如今编译运行,就不会再出现错误了。固然,咱们还能够经过将第三个参数替换成本身的 UIApplication 子类,这样咱们就能够轻易地作一些控制整个应用行为的事情了。好比将 main.swift 的内容换成
UIApplicationMain(
CommandLine.argc,
UnsafeMutableRawPointer(CommandLine.unsafeArgv)
.bindMemory(
to: UnsafeMutablePointer<Int8>.self,
capacity: Int(CommandLine.argc)),
NSStringFromClass(MyApplication.self),
NSStringFromClass(AppDelegate.self)
)
import UIKit
class MyApplication: UIApplication {
override func sendEvent(_ event: UIEvent) {
super.sendEvent(event)
print("Event sent:\(event)")
}
}
let cls = MyClass()
cls.mustProtocolMethod()
cls.mustProtocolMethod1()
复制代码
这样每次发送事件 (好比点击按钮) 时,咱们均可以监听到这个事件了
Objective-C 中的 protocol
里存在 @optional
关键字,被这个关键字修饰的方法并不是必需要被实现。咱们能够经过协议定义一系列方法,而后由实现协议的类选择性地实现其中几个方法。最好的例子我想应该是 UITableViewDataSource 和 UITableViewDelegate。前者中有两个必要方法
-tableView:numberOfRowsInSection:
-tableView:cellForRowAtIndexPath:
复制代码
原生的 Swift protocol 里没有可选项,全部定义的方法都是必须实现的
protocol MyProtocol {
func mustProtocolMethod() //必须实现方法
func mustProtocolMethod1() //必须实现方法
}
class MyClass: MyProtocol {
func mustProtocolMethod() {
print("MyClass-->必须实现方法:mustProtocolMethod")
}
func mustProtocolMethod1() {
print("MyClass-->必须实现方法:mustProtocolMethod1")
}
}
复制代码
若是咱们想要像 Objective-C 里那样定义可选的协议方法,就须要将协议自己和可选方法都定义为Objective-C 的,也即在 protocol
定义以前以及协议方法以前加上 @objc
。另外和 Objective-C 中的 @optional
不一样,咱们使用没有 @
符号的关键字 optional
来定义可选方法
@objc protocol MyProtocol1 {
@objc optional func optionalProtocolMethod() //可选方法
func mustProtocolMethod1() //必须实现方法
}
class MyClass1: MyProtocol1 {
func mustProtocolMethod1() {
print("MyClass1-->必须实现方法:mustProtocolMethod1")
}
}
let cls1 = MyClass1()
cls1.mustProtocolMethod1()
复制代码
一个不可避免的限制是,使用 @objc 修饰的 protocol 就只能被 class 实现了
,也就是说,对于 struct 和 enum 类型,咱们是没法令它们所实现的协议中含有可选方法或者属性的
在 Swift 2.0 中,咱们有了另外一种选择,那就是使用 protocol extension。咱们能够在声明一个 protocol 以后再用 extension 的方式给出部分方法默认的实现。这样这些方法在实际的类中就是可选实现的了
protocol MyProtocol2 {
func optionalProtocolMethod1() //可选方法
func optionalProtocolMethod2() //可选方法
func mustProtocolMethod1() //必须实现方法
}
extension MyProtocol2{
func optionalProtocolMethod1(){}
func optionalProtocolMethod2(){}
}
复制代码
跟OC同样,Swift也是采用基于引用计算的ARC内存管理方案(针对堆空间)
Swift中ARC有3种引用
weak
):经过weak
定义弱引用
nil
nil
时,不会触发属性观察器unowned
):经过unowned
定义无主引用
unsafe_unretained
)Fatal error: Attempted to read an unowned reference but object 0x10070a460 was already deallocated
class Person {
func eat() {
}
deinit {
print("Person销毁")
}
}
unowned var p = Person()
p.eat()
复制代码
这段代码就会产生运行时错误
循环引用
weak、unowned
都能解决循环引用的问题,unowned
要比weak
少一些性能消耗
闭包的循环引用
class Person {
var fn:(() -> ())?
func run() {
print("run")
}
deinit {
print("Person销毁")
}
}
func test() {
let p = Person()
p.fn = {
p.run()
}
}
test()
复制代码
下面这段代码就会形成循环引用,想要解决这个问题,可使用weak或者unowned
func test() {
let p = Person()
p.fn = {[weak p] in
p?.run()
}
}
func test() {
let p = Person()
p.fn = {[unowned p] in
p.run()
}
}
复制代码
若是想在定义闭包属性的同时引用self
,这个闭包必须是lazy
的,由于在实例初始化完毕后才能引用self
class Person {
lazy var fun:(() -> ()) = {
[weak self] in
self?.run()
}
func run() {
print("run")
}
deinit {
print("Person销毁")
}
}
复制代码
闭包fn
内部若是用到了实例成员,属性,方法,编译器会强制要求明确的写出self
【注】:编译器强制要求明确的写出self
的时候有可能会致使循环引用,须要注意的
若是lazy
属性是闭包调用的结果,那么不用考虑循环引用问题,(由于闭包调用后,闭包的声明周期就结束了)
class Person {
var age: Int = 0
lazy var getAge: Int = {
self.age
}()
deinit {
print("Person销毁")
}
}
复制代码
内存(RAM)中有两个区域,栈区(stack)和堆区(heap)。在 Swift 中,值类型,存放在栈区;引用类型,存放在堆区。
值类型(Value Type)
值类型,即每一个实例保持一份数据拷贝
在 Swift 中,典型的有 struct,enum,以及 tuple 都是值类型。而平时使用的 Int, Double,Float,String,Array,Dictionary,Set 其实都是用结构体实现的,也是值类型。
Swift 中,值类型的赋值为深拷贝(Deep Copy),值语义(Value Semantics)即新对象和源对象是独立的,当改变新对象的属性,源对象不会受到影响,反之同理。
struct CoordinateStruct {
var x: Double
var y: Double
}
var coordA = CoordinateStruct(x: 0, y: 0)
var coordB = coordA
coordA.x = 100.0
print("coordA.x -> \(coordA.x)")
print("coordB.x -> \(coordB.x)")
复制代码
若是声明一个值类型的常量,那么就意味着该常量是不可变的(不管内部数据为 var/let)
let coordC = CoordinateStruct(x: 0, y: 0)
复制代码
在 Swift 3.0 中,可使用 withUnsafePointer(to:_:)
函数来打印值类型变量的内存地址,这样就能看出两个变量的内存地址并不相同。
withUnsafePointer(to: &coordA) { print("\($0)") }
withUnsafePointer(to: &coordB) { print("\($0)") }
0x0000000100007670
0x0000000100007680
复制代码
在 Swift 中,双等号(== & !=
)能够用来比较变量存储的内容是否一致,若是要让咱们的 struct
类型支持该符号,则必须遵照Equatable
协议。
extension CoordinateStruct: Equatable {
static func ==(left: CoordinateStruct, right: CoordinateStruct) -> Bool {
return (left.x == right.x && left.y == right.y)
}
}
if coordA != coordB {
print("coordA != coordB")
}
复制代码
引用类型(Reference Type)
引用类型,即全部实例共享一份数据拷贝
在 Swift 中,class 和闭包是引用类型。引用类型的赋值是浅拷贝(Shallow Copy)
,引用语义(Reference Semantics)即新对象和源对象的变量名不一样,但其引用(指向的内存空间)是同样的
,所以当使用新对象操做其内部数据时,源对象的内部数据也会受到影响。
class Dog {
var height = 0.0
var weight = 0.0
}
var dogA = Dog()
var dogB = dogA
dogA.height = 50.0
print("dogA.height -> \(dogA.height)")
print("dogB.height -> \(dogB.height)")
// dogA.height -> 50.0
// dogB.height -> 50.0
复制代码
在 Swift 3.0 中,可使用如下方法来打印引用类型变量指向的内存地址。从中便可发现,两个变量指向的是同一块内存空间。
print(Unmanaged.passUnretained(dogA).toOpaque())
print(Unmanaged.passUnretained(dogB).toOpaque())
//0x0000000100772ff0
//0x0000000100772ff0
复制代码
在 Swift 中,三等号(=== & !==
)能够用来比较引用类型的引用(即指向的内存地址)是否一
致。也能够在遵照 Equatable 协议后,使用双等号(== & !=
)用来比较变量的内容是否一致。
简单来讲:没有特别须要,尽量的仍是使用String
,有如下三个缘由
String
和 NSString
有着良好的互相转换的特性,可是如今 Cocoa 全部的 API 都接受和返回 String
类型。咱们没有必要也没必要给本身凭空添加麻烦去把框架中返回的字符串作一遍转换String是struct
,相比起 NSObject 的 NSString 类来讲,更切合字符串的 "不变" 这一特性。经过配合常量赋值 (let) ,这种不变性在多线程编程时就很是重要了,它从原理上将程序员从内存访问和操做顺序的担心中解放出来。另外,在不触及 NSString 特有操做和动态特性的时候,使用 String 的方法,在性能上也会有所提高for...in
的枚举GCD中Swift和OC都差很少,为了方便使用,咱们能够简单封装如下GCD
typealias Task = (_ cancel : Bool) -> Void
@discardableResult
func delay(_ time: TimeInterval, task: @escaping ()->()) -> Task? {
func dispatch_later(block: @escaping ()->()) {
let t = DispatchTime.now() + time
DispatchQueue.main.asyncAfter(deadline: t, execute: block)
}
var closure: (()->Void)? = task
var result: Task?
let delayedClosure: Task = {
cancel in
if let internalClosure = closure {
if (cancel == false) {
DispatchQueue.main.async(execute: internalClosure)
}
}
closure = nil
result = nil
}
result = delayedClosure
dispatch_later {
if let delayedClosure = result {
delayedClosure(false)
}
}
return result;
}
func cancel(_ task: Task?) {
task?(true)
}
复制代码
向一个对象发出询问,以肯定他是否是属于某个类,这种操做就称为自省。
在OC中一个对象询问它是否是属于某个类。经常使用的方法有下面两类
OC方法
[obj1 isKindOfClass:[ClassA class]];
[obj2 isMemberOfClass:[ClassB class]];
复制代码
-isKindOfClass:
判断 obj1 是不是 ClassA 或者其子类的实例对象;isMemberOfClass:
则对 obj2 作出判断,当且仅当 obj2 的类型为 ClassB 时返回为真Swift方法
class ClassA: NSObject {}
class ClassB: ClassA {}
let obj1 = ClassA()
let obj2 = ClassB()
print(obj1.isKind(of: ClassA.self))
print(obj2.isMember(of: ClassA.self))
//true
//false
复制代码
对于一个不肯定的类型,咱们如今可使用 is
来进行判断。is
在功能上至关于原来的 isKindOfClass
,能够检查一个对象是否属于某类型或其子类型。is
和原来的区别主要在于亮点,首先它不只能够用于 class
类型上,也能够对 Swift 的其余像是 struct
或enum
类型进行判断
class ClassA { }
class ClassB: ClassA { }
let obj: AnyObject = ClassB()
if (obj is ClassA) {
print("属于 ClassA")
}
if (obj is ClassB) {
print("属于 ClassB")
}
复制代码
在Swift中KVO仅限于NSObject的子类,咱们还须要作额外的工做,那就是将想要观测的对象标记为 dynamic 和 @objc
在 Swift 4 以前的版本中,为一个 NSObject 的子类实现 KVO 的最简单的例子看起来是这样的
class MyClass: NSObject {
@objc dynamic var date = Date()
}
private var myContext = 0
class Class: NSObject {
var myObject: MyClass!
override init() {
super.init()
myObject = MyClass()
print("初始化 MyClass,当前日期: \(myObject.date)")
myObject.addObserver(self,
forKeyPath: "date",
options: .new,
context: &myContext)
delay(3) {
self.myObject.date = Date()
}
}
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?)
{
if let change = change, context == &myContext {
if let newDate = change[.newKey] as? Date {
print("MyClass 日期发生变化 \(newDate)")
}
}
}
}
let obj = Class()
初始化 MyClass,当前日期: 2020-04-08 07:26:22 +0000
MyClass 日期发生变化 2020-04-08 07:26:25 +0000
复制代码
Swift 4 中 Apple 引入了新的 KeyPath
的表达方式,如今,对于类型 Foo
中的变量 bar: Bar
,对应的 KeyPath
能够写为 \Foo.bar
class AnotherClass: NSObject {
var myObject: MyClass!
var observation: NSKeyValueObservation?
override init() {
super.init()
myObject = MyClass()
print("初始化 AnotherClass,当前日期: \(myObject.date)")
observation = myObject.observe(\MyClass.date, options: [.new]) { (_, change) in
if let newDate = change.newValue {
print("AnotherClass 日期发生变化 \(newDate)")
}
}
delay(1) { self.myObject.date = Date() }
}
}
复制代码
使用Swift 4.0 KeyPath的好处有不少
Swift 中使用 KVO 仍是有有两个显而易见的问题
dynamic 和 @objc
进行修饰,有时候咱们极可能也没法修改想要观察的类的源码,遇到这种状况,一个可行的方案是继承这个类,而且将须要观察的属性使用dynamic 和 @objc
重写class MyClass: NSObject {
var date = Date()
}
class MyChildClass: MyClass {
@objc dynamic override var date: Date {
get { return super.date }
set { super.date = newValue }
}
}
复制代码
C 系语言中在方法内部咱们是能够任意添加成对的大括号 {} 来限定代码的做用范围的。这么作通常来讲有两个好处,首先是超过做用域后里面的临时变量就将失效,这不只可使方法内的命名更加容易,也使得那些不被须要的引用的回收提早进行了,能够稍微提升一些代码的效率;另外,在合适的位置插入括号也利于方法的梳理,对于那些不太方便提取为一个单独方法,可是又应该和当前方法内的其余部分进行一些区分的代码,使用大括号能够将这样的结构进行一个相对天然的划分
OC代码
- (void)loadView {
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
{
UILabel *titleLabel = [[UILabel alloc]
initWithFrame:CGRectMake(150, 30, 200, 40)];
titleLabel.textColor = [UIColor redColor];
titleLabel.text = @"Title";
[view addSubview:titleLabel];
}
{
UILabel *textLabel = [[UILabel alloc]
initWithFrame:CGRectMake(150, 80, 200, 40)];
textLabel.textColor = [UIColor redColor];
textLabel.text = @"Text";
[view addSubview:textLabel];
}
self.view = view;
}
复制代码
Swift方法
在 Swift 中,直接使用大括号的写法是不支持的,由于这和闭包的定义产生了冲突。若是咱们想相似地使用局部 scope
来分隔代码的话,一个不错的选择是定义一个接受 ()->()
做为函数的全局方法,而后执行它
override func loadView() {
let view = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
view.backgroundColor = .white
local {
let titleLabel = UILabel(frame: CGRect(x: 150, y: 30, width: 200, height: 40))
titleLabel.textColor = .red
titleLabel.text = "Title"
view.addSubview(titleLabel)
}
local {
let textLabel = UILabel(frame: CGRect(x: 150, y: 80, width: 200, height: 40))
textLabel.textColor = .red
textLabel.text = "Text"
view.addSubview(textLabel)
}
self.view = view
}
复制代码
咱们还可使用匿名闭包来实现
override func loadView() {
let view = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
view.backgroundColor = .white
let titleLabel: UILabel = {
let label = UILabel(frame: CGRect(x: 150, y: 30, width: 200, height: 40))
label.textColor = .red
label.text = "Title"
return label
}()
view.addSubview(titleLabel)
let textLabel: UILabel = {
let label = UILabel(frame: CGRect(x: 150, y: 80, width: 200, height: 40))
label.textColor = .red
label.text = "Text"
return label
}()
view.addSubview(textLabel)
self.view = view
}
复制代码
咱们常常会遇到给分类添加成员变量的问题,对于这类问题,OC的写法你们都是耳熟能详了。譬如给UIView
添加一个viewId
的成员变量
#import <objc/runtime.h>
static const void *RunTimeViewID = @"RunTimeViewID";
@implementation UIView (JHExtension)
- (NSString *)viewID{
NSString *ID = objc_getAssociatedObject(self, &RunTimeViewID);
return ID;
}
- (void)setViewID:(NSString *)viewID{
objc_setAssociatedObject(self, &RunTimeViewID, viewID, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
复制代码
Swift
在 Swift 中这样的方法依旧有效,只不过在写法上可能有些不一样。两个对应的运行时的 get 和 set Associated Object 的 API 是这样的
func objc_getAssociatedObject(object: AnyObject!,
key: UnsafePointer<Void>
) -> AnyObject!
func objc_setAssociatedObject(object: AnyObject!,
key: UnsafePointer<Void>,
value: AnyObject!,
policy: objc_AssociationPolicy)
复制代码
struct RunTimeViewKey {
static let RunTimeViewID = UnsafeRawPointer.init(bitPattern: "RunTimeViewID".hashValue)
}
extension UIView {
var ViewID: String? {
set {
objc_setAssociatedObject(self, RunTimeViewKey.RunTimeViewID!, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
get {
return objc_getAssociatedObject(self, RunTimeViewKey.RunTimeViewID!) as? String
}
}
}
复制代码
无并发,不编码。而只要一说到多线程或者并发的代码,咱们可能就很难绕开对于锁的讨论。简单来讲,为了在不一样线程中安全地访问同一个资源,咱们须要这些访问顺序进行
OC方法
- (void)myMethod:(id)anObj {
@synchronized(anObj) {
// 在括号内持有 anObj 锁
}
}
复制代码
Swift方法
在Swift中去掉了synchronized
方法,其实 @synchronized
在幕后作的事情是调用了objc_sync 中的 objc_sync_enter
和 objc_sync_exit
方法,而且加入了一些异常判断。所以,在 Swift 中,若是咱们忽略掉那些异常的话,咱们想要 lock
一个变量的话
//定义一个闭包
func synchronized(_ lock: AnyObject, closure: () -> ()) {
objc_sync_enter(lock)
closure()
objc_sync_exit(lock)
}
func myMethodLocked(anObj: AnyObject!) {
synchronized(anObj) {
// 在括号内持有 anObj 锁
}
}
复制代码
举一个具体的使用例子,好比咱们想要为某个类实现一个线程安全的 setter,能够这样进行重写
class Obj {
var _str = "123"
var str: String {
get {
return _str
}
set {
synchronized(self) {
_str = newValue
}
}
// 下略
}
}
复制代码
相比于 Objective-C,Swift 最大的改变就在于方法调用上的优化
。
OC方法调用
在 Objective-C 中,全部的对于 NSObject 的方法调用在编译时会被转为 objc_msgSend
方法。这个方法运用 Objective-C 的运行时特性,使用派发的方式在运行时对方法进行查找
。由于 Objective-C 的类型并非编译时肯定的,咱们在代码中所写的类型不过只是向编译器的一种“建议”
,不论对于怎样的方法,这种查找的代价基本都是一样的
这个过程的等效的表述可能相似这样 (注意这只是一种表述,与实际的代码和工做方式无关)
methodToCall = findMethodInClass(class, selector);
// 这个查找通常须要遍历类的方法表,须要花费必定时间
methodToCall(); // 调用
复制代码
Swift方法调用
Swift 由于使用了更安全和严格的类型,若是咱们在编写代码中指明了某个实际的类型的话 (注意,须要的是实际具体的类型,而不是像 Any 这样的抽象的协议),咱们就能够向编译器保证在运行时该对象必定属于被声明的类型
由于有了更多更明确的类型信息,编译器就能够在类型中处理多态时创建虚函数表 (vtable),这是一个带有索引的保存了方法所在位置的数组。在方法调用时,与原来动态派发和查找方法不一样,如今只须要经过索引就能够直接拿到方法并进行调用了,这是实实在在的性能提高。这个过程大概至关于:
let methodToCall = class.vtable[methodIndex]
// 直接使用 methodIndex 获取实现
methodToCall(); // 调用
复制代码
更进一步,在肯定的状况下,编译器对 Swift 的优化甚至能够作到将某些方法调用优化为 inline 的形式。好比在某个方法被 final 标记时,因为不存在被重写的可能,vtable 中该方法的实现就彻底固定了。对于这样的方法,编译器在合适的状况下能够在生成代码的阶段就将方法内容提取到调用的地方,从而彻底避免调用