iOS开发iOS面试题

iOS 开发工具 - Swift VS Objective-C

2019-01-19  本文已影响54人  四月_Hsu

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 将协议(protocol)中的部分方法设计成可选(optional)

关键词: #协议 #协议扩展

@optional 和 @required 是 Objective-C 中特有的关键字。

在 Swift 中,默认协议中的所有方法都是必须实现的。而且在协议中,方法不能直接被设置为 optional。下面给出两种解决方案:

协议的代码实战

关键词: #协议

下面代码有什么问题?

protocol SomeProtocolDelegate {
    func doSomething()
}

class SomeClss {
    weak var delegate: SomeProtocolDelegate?
}

在上面代码中,协议属性定义会报错。

Swift 中的协议不仅能被 class 这样的引用类型实现,也能被 struct、enum 这样的值类型实现(这是和 Objective-C 最大的不同)。weak 关键字用于 ARC 环境下,为引用类型提供引用计数这样的内存管理,他是不能被用来修饰值类型的。

上面的代码有两种修正方法。

需要注意的是,以上两种修改后,该协议不能再被 struct、enum 等引用类型实现。

编程思路

在 Swift 和 Objective-C 的混编项目中,如何在 Swift 文件中调用 Objective-C 文件中定义的方法?如何在 Objective-C 文件中调用 Swift 文件中定义的方法

关键词: #头文件 #@objc

加分回答:

比较 Swift 和 Objective-C 中的初始化方法(init)有什么异同

关键词: #初始化

一言以蔽之,Swift 中的初始化方法更加严格和准确。

比较 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],经历了两个阶段:

所以,整个流程是:编译器翻译 -> 给接收者发送消息 -> 接收者响应消息。

例如,在 [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);对象不为空,却找不到对应的方法、

什么事 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);
    }

加分回答:

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    

下面解释下这段代码:

其中,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)
        }
    }
}
上一篇下一篇

猜你喜欢

热点阅读