美文网首页iOS开发iOS面试题
iOS 开发工具 - Swift VS Objective-C

iOS 开发工具 - Swift VS Objective-C

作者: 四月_Hsu | 来源:发表于2019-01-19 17:04 被阅读54次

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)
        }
    }
}

相关文章

网友评论

    本文标题:iOS 开发工具 - Swift VS Objective-C

    本文链接:https://www.haomeiwen.com/subject/zgszdqtx.html