1. Swift
2. Objective-C
3. Swift VS Objective-C
4. Xcode 使用
3. Swift VS Objective-C
本节从数据结构、编程思路和语言特性三个角度来对比 Swift 和 Objective-C 这两种语言的异同。
数据结构
Swift 为什么将 String、Array 和 Dictionary 设计成值类型
关键词: #引用类型
#值类型
#多线程
#协议
要回答 Swift 为什么将 String、Array 和 Dictionary 设计成值类型,就要和 Objective-C 中相同的数据类型作对比。Objective-C 中 String、Array 和 Dictionary 皆被设计成引用类型。
-
值类型相比于引用类型,最大的优势在于可以更高效的使用内存。值类型在栈上操作,引用类型在堆上操作。栈上的操作仅仅是单个指针的上下移动,而堆上的操作则牵涉合并、移位、重新链接等,也就是说,Swift 这样设计大幅减少了堆上内存的分配和回收的次数。同时,copy-on-write 又将值传递和复制的开销降到最低。
-
Swift 将 String、Array 和 Dictionary 设计成值类型也是为了线程安全。通过 Swift 的 let 设置,使得这些数据达到真正意义上的“不变”,也从根本上解决了多线程的内存访问和操作的顺序问题。
-
Swift 将 String、Array 和 Dictionary 设计成值类型还能提升 API 的灵活度。例如,通过实现 Collection 这样的协议,可以遍历 String,使得整个开发更加灵活高效。
如何用 Swift 将协议(protocol)中的部分方法设计成可选(optional)
关键词: #协议
#协议扩展
@optional 和 @required 是 Objective-C 中特有的关键字。
在 Swift 中,默认协议中的所有方法都是必须实现的。而且在协议中,方法不能直接被设置为 optional。下面给出两种解决方案:
-
在协议和方法前都加上 @objc 关键字,然后在方法前加上 optional 关键字。该方法实际上是把协议转化为 Objective-C 方法,然后进行可选定义。示例如下:
@objc protocol SomeProtocol { func requiredMethod() @objc optional func optionalMethod() }
-
用扩展(extension)来规定可选方法。在 Swift 中,协议扩展可以定义部分方法的默认实现。这样,这些方法在实际调用中就是可选实现了。示例如下:
protocol SomeProtocol { func requiredMethod() func optionalMethod() } extension SomeProtocol { func optionalMethod() { print("已经默认实现了") } }
协议的代码实战
关键词: #协议
下面代码有什么问题?
protocol SomeProtocolDelegate {
func doSomething()
}
class SomeClss {
weak var delegate: SomeProtocolDelegate?
}
在上面代码中,协议属性定义会报错。
Swift 中的协议不仅能被 class 这样的引用类型实现,也能被 struct、enum 这样的值类型实现(这是和 Objective-C 最大的不同)。weak 关键字用于 ARC 环境下,为引用类型提供引用计数这样的内存管理,他是不能被用来修饰值类型的。
上面的代码有两种修正方法。
-
在 protocol 前面加上 @objc。在 Objective-C 中,协议只能由 class 来实现,这样一来,weak 修饰的对象就与 Objective-C 一样,只不过是 class 类型,修正如下:
@objc protocol SomeProtocolDelegate { func doSomething() }
-
在 SomeProtocolDelegate 后添加关键词 class。如此一来,就声明了该协议只能由 class 来实现,代码修正如下:
protocol SomeProtocolDelegate: class { func doSomething() }
需要注意的是,以上两种修改后,该协议不能再被 struct、enum 等引用类型实现。
编程思路
在 Swift 和 Objective-C 的混编项目中,如何在 Swift 文件中调用 Objective-C 文件中定义的方法?如何在 Objective-C 文件中调用 Swift 文件中定义的方法
关键词: #头文件
#@objc
-
在 Swift 中, 若要使用 Objective-C 代码,则可以再桥接文件(通常明明 ProjectName-Bridging-Header.h)文件中添加 Objective-C 的头文件名称,这样即可以在 Swift 文件中调用 Objective-C 代码。一般情况下,Xcode 会在 Swift 项目中第一次创建 Objective-C 文件时,提示用户是否创建 ProjectName-Bridging-Header.h 文件。
-
在 Objective-C 中,若要调用 Swift 代码,则可以导入 Swift 生成的头文件 ProjectName-Swift.h 来实现。
加分回答:
- 在 Swift 文件中,若要将规定的固定的属性或者方法暴露给 Objective-C 使用,则可以在方法或者属性前加上 @objc 来声明。如果该类是 NSObject 的子类,那么 Swift 会在非 private 的方法或属性前自动加上 @objc。
比较 Swift 和 Objective-C 中的初始化方法(init)有什么异同
关键词: #初始化
一言以蔽之,Swift 中的初始化方法更加严格和准确。
-
在 Objective-C 中,初始化无法保证全部成员变量都初始化;编译器会对属性设置而无警告,但是在实际操作中会出现初始化不完全的问题。初始化方法与普通方法并无实际差别,可以多次调用。
-
在 Swift 中,初始化方法必须保证所有非 optional 的成员变量都完成初始化;同时,新增 convenience 和 required 两个修饰初始化方法的关键词 -- convenience 只是提供一种方便的初始化方法,必须通过调用同一个类中的 designated 初始化方法来完成。required 是强制子类重写父类中所修饰的初始化方法。
比较 Swift 和 Objective-C 中的协议(protocol)有什么异同
关键词: #协议
Swift 和 Objective-C 中协议的相同点在于:两者都可以被用作代理。Objective-C 中的 protocol 类似 Java 中的 Interface,在实际开发中用于用于适配器模式(Adapter Pattern)。
Swift 和 Objective-C 中协议的不同点在于:Swift 中的 protocol 还可以对接口进行抽象,例如 Sequence,配合扩展(extension)、泛型、关联类型等可以实现面向协议编程,从而大大提高这个代码的灵活性。同时,Swift 中的 protocol 还可以用于值类型,如结构体和枚举。
语言特性
谈谈对 Objective-C 和 Swift 动态性的理解
关键词: #动态特性
#runtime
#面向协议编程
runtime 其实就是 Objective-C 的动态机制。runtime 执行的是编译后的代码,这时它可以动态加载对象,添加方法、修改属性、传递信息等。具体过程是:在 Objective-C 中,对象调用方法时,如 [self.tableView reload],经历了两个阶段:
-
编译阶段: 编译器(compiler)会把这句话翻译成 objc_msgSend(self.tableView, @selector(reload)),把消息发送给 self.tableView。
-
运行阶段:接受者 self.tableView 会响应这个消息,其间可能会直接执行、转发消息,也可能会找不到这方法而导致程序崩溃。
所以,整个流程是:编译器翻译 -> 给接收者发送消息 -> 接收者响应消息。
例如,在 [self.tableView reload] 中,self.tableView 就是接收者,reload 就是消息,所以,方法调用的的格式在编译器看来是 [receiver message]。
其中,接受者如何响应消代码,就发生在运行时(runtime)。runtime 执行的是编译后的代码,这时,它可以动态加载对象、添加方法、修改属性、传递信息等。runtime 的运行机制,就是 Objective-C 的动态特性。
Swift 目前被公认是一门静态语言。它的动态特性都是通过桥接 OC 来实现的。如果要把其动态性写的更 “Swift” 一点,则可以用 protocol 来处理,比如,可以将 OC 中的 reflection 这样写:
if ([someImage respondsToSelector: @selector(shake)]) {
[someImage performSelector: shake];
}
在 Swift 中可以这样写:
if let shakeableImage = someImage as? Shakable {
shakeableImage.shake()
}
语言特性的代码实战
关键词: #动态特性
#协议
#扩展
看看下面代码会输出什么?
protocol Chef {
func makeFood()
}
extension Chef {
func makeFood() {
print("Make Food")
}
}
struct SeafoodChef: Chef {
func makeFood() {
print("Cook Seafood")
}
}
let chefOne: Chef = SeafoodChef()
let chefTwo: SeafoodChef = SeafoodChef()
chefOne.makeFood()
chefTwo.makeFood()
上面代码会打印出两行 "Cook Seafood"。
在 Swift 中,协议中的是动态派发,扩展中是静态派发。也就是说,协议中如果有方法声明,那么方法会根据对象的实际类型进行调用。
此题中的 makeFood() 方法在 Chef 协议中已经声明了,而 chefOne 虽然声明为 Chef,但实际实现为 SeafoodChef。所以,根据实际情况,makeFood() 会调用 SeafoodChef 中的实现,chefTwo 也是同样的道理。
追问:如果 Protocol 中没有声明 makeFood() 方法,代码又会输出什么?
代码会答应出两行结果:第一行是 “Make food”,第二行是 “Cook Seafood”。
因为协议中没有声明 makeFood() 方法,所以此时会按照扩展中的声明类型进行静态派发。也就是说,会根据对象的声明类型进行调用。chefOne 被声明为 Chef,所以调用扩展中的实现。chefTwo 被声明为 SeafoodChef,所以调用 SeafoodChef 的实现。
message send 如果找不到对象,则会如何进行后续处理
关键词: #动态特性
Message send 找不到对象分两种情况:对象为空(nil);对象不为空,却找不到对应的方法、
-
对象为空时,Objective-C 向 nil 发送消息时有效的,在 runtime 中不会产生任何效果。如果消息中的方法返回值是对象,那么给 nil 发送消息返回 nil。如果消息中的方法返回值是结构体,那么给 nil 发送消息返回0。
-
对象不为空,却找不到对应的方法,时,程序有异常,引发 unrecognized selector。
什么事 method swizzling
关键词: #动态特性
每个类都会维护一个方法列表,其中方法名与其实现一一对应。即 SEL(方法名)和 IMP(指向实现的指针)的对应关系。method swizzling 可以在 runtime 时将 SEL 和 IMP 进行更换。比如, SELa 原来对应 IMPa,SELb 原来对应 IMPb,而在 method swizzling 之后,SELa 就可以对应 IMPb,SELb 可以对应 IMPa。下面是一个封装好的动态实现:
// 方法一中的 SEL 和 Method SEL
SEL oneSEL = @selector(methodOne:);
Method oneMethod = class_getInstanceMethod(selfClass, oneSEL);
// 方法二中的 SEL 和 Method SEL
SEL twoSEL = @selector(methodTwo:);
Method twoMethod = class_getInstanceMethod(selfClass, twoSEL);
// 给方法一添加实现,可以避免方法一直没实现
BOOL addSucc = class_addMethod(selfClass, oneSEL, method_getImplementation(twoMethod), method_getTypeEncoding(twoMethod));
if (addSucc) {
// 添加成功,将方法一的实现替换到方法二
class_replaceMethod(selfClass, twoSEL, method_getImplementation(oneMethod), method_getTypeEncoding(twoMethod));
} else {
// 添加失败:方法一已经有实现,直接将方法一和方法二的实现交换
method_exchangeImplementations(oneMethod, twoMethod);
}
加分回答:
-
方法交换应该保持唯一性和原子性。唯一性是指应该尽可能在 +load 方法中实现,这样可以保证方法一一定会被调用且不会出现异常。原子性是指使用 dispatch_once 来执行方法交换,这样可以保证只运行一次。
-
不要轻易使用 method swizzling。因为动态交换方法的实现并没有编译器的安全保证,可能会在运行时造成奇怪的问题。
Swift 和 Objective-C 的自省(Introspection)有什么不同
关键词: #动态特性
自省在 Objective-C 中就是:判断某个对象是否属于某个类的操作。它有一下两种形式:
[obj isKindOfClass: [SomeClass class]];
[obj isMemberOfClass: [SomeClass class]];
在上面的代码中,第一行中的代码中的 isKindOfClass 用于判断 obj 是否是 SomeClass 或其子类的实例对象。第二行代码中的 isMemberOfClass 则对 obj 作出判断,当且仅当 obj 是 SomeClass(非子类)的实例对象时,才返回真。这两个方法的使用有一个前提,即 obj 必须是 NSObject 或其子类。
在 Swift 中,由于很多 class 并非继承自 NSObject,故而 Swift 用 is 函数来进行判断,它相当于 isKindOfClass。这样做的优点是 is 函数斌可以用于任何 class 类型上,也可以用来判断 enume 和 struct 类型。
在 Swift 中继承自 NSObject 的类型,依然可以用 isKindOf 和 isMemberOf。这点与 Objective-C 相同。
加分回答:
自省经常与动态类性一起使用。动态类型就是 id 类型,任何类型的对象都可以用 id 来代指,这个时候常常用自省来判断对象的实际所属类型,如下:
id vehicle = SomeCarInstace;
if ([vehicle isKindOfClass: [Car class]]) {
NSLog(@"vehicle is a Car");
if ([vehicle isKindOfClass: [Tesla class]]) {
NSLog(@"vehicle is a Tesla");
}
} else if ([vehicle isKindOfClass: [Truck class]]) {
NSLog(@"vehicle is a Truck");
}
能否通过 Category 给已有的类添加属性(property)
关键词: #动态特性
可以通过 Category 给已有的类添加属性(property),无论是对 Objective-C 还是 Swift 而言。
在 Objective-C 中,正常情况下,给 Category 中添加属性会报错,提示找不到 setter 和 getter 方法,这是因为在 Category 中不会自动生成这两个方法。解决的方法就是引入运行时头文件,并配合关联对象的方法来实现。其中主要涉及的两个函数是 objc_getAssocicatedObject 和 objc_setAssociatedObject。在 Swift 中,解决方法与在 Objective-C 中相同,只是在写法上更加 Swift 化。
假如有个 class 叫 User。由于此 App 要打开国际市场,所以,产品经理要去 User 能满足能中间名字的外国人。于是我们回想在它的 Category 里添加 middleName 属性。实例的 Objective-C diamante如下:
// .h
#import "User.h"
@interface User (Foreign)
@property (nonatomic, copy) NSString *middleName;
@end
// .m
#import "User+Foreign.h"
#import <objc/runtime.h>
static void *middleNameKey = &middleNameKey;
@implementation User (Foreign)
- (void)setMiddleName:(NSString *)middleName {
objc_setAssociatedObject(self, &middleNameKey, middleName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)middleName {
return objc_getAssociatedObject(self, &middleNameKey);
}
@end
下面解释下这段代码:
-
在 .h 文件中添加属性。
-
在 .m 文件中引入运行时头文件 <objc/runtime.h>,接着设置关联属性的 Key,最后实现 setter 和 getter。
其中,objc_setAssociatedObject 这个方法的四个参数分别为原对象、关联属性 Key、关联属性和关联策略。具体细节可参考苹果官方文档 API。
用 Swift 实现类似功能是这样的:
import Foundation
class User {
}
private var middleNameKey: Void?
extension User {
var middleName: String? {
get {
return objc_getAssociatedObject(self, &middleNameKey) as? String
}
set {
objc_setAssociatedObject(self, &middleNameKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
}
}
网友评论