热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

在Cocoa框架中使用Swift的一些注意事项

虽然说Swift是作为一种全新的语言被推出的,但是不可避免的需要借助于Apple生态来对它进行推广,在推广的过程中,就不可避免的需要被使用

虽然说Swift是作为一种全新的语言被推出的,但是不可避免的需要借助于Apple生态来对它进行推广,在推广的过程中,就不可避免的需要被使用在Cocoa框架中,所以我们今天来总结一下当Swift被使用在Cocoa框架中时需要注意的一些事项。

在我们开始讨论之前,我们先来了解一下Swift与Objective-C的一些不同点。

区别

我们通过使用Swift与Objective-C来编写具有一个存储属性、一个计算属性、一个实例方法的类:

Objective-C

@interface OCModel : NSObject///存储属性
@property (nonatomic, copy) NSString *privateName;///计算属性
@property (nonatomic, copy) NSString *publicName;///实例方法
- (void)showName;@end@implementation OCModel- (void)setPublicName:(NSString *)publicName {self.privateName = publicName;
}- (NSString *)publicName {return self.privateName;
}///实例方法
- (void)showName {NSLog(@"OCModel name is %@", self.privateName);
}@end

Swift

class SwiftModel {///存储属性var privateName: String?///计算属性var publicName: String {get {return privateName ?? "none"}set {privateName = newValue}}///实例方法func showName() -> Void {print("SwiftModel name is \(privateName ?? "none")")}
}

现在我们对两种语言定义的类通过Runtime来读取一下相关的内容,结果如下:

Objective-C

Objective-C

Swift

Swift

从上面结果可见,Swift中的属性即方法并不能通过Runtime机制读取出,这是因为Runtime的API是基于运行时机制的,而Swift本身是静态语言,在编译时就已经确定变量、方法等内容。

但我们在使用Objective-C进行iOS开发时,或多或少的都使用到了Runtime,同时Runtime也可以为我们解决许多问题,那么在Swift中就无法使用Runtime了吗?当然不是。

Swift中使用Runtime

我们了解了Swift为何无法使用Runtime的原因,要解决这个问题,最简单的方法就是将Swift中想要使用Runtime的内容桥接至Objective-C上,所幸苹果已经为我们做到了这一点,那就是@objc关键字。

我们将上述Swift中的类桥接至Objective-C之后看一下结果:

桥接之后

@objc class SwiftModel: NSObject {///存储属性@objc var privateName: String?///计算属性@objc var publicName: String {get {return privateName ?? "none"}set {privateName = newValue}}///实例方法@objc func showName() -> Void {print("SwiftModel name is \(privateName ?? "none")")}
}

结果

Swift桥接至Objective-C

可见,在将Swift桥接至Objective-C之后,我们可以使用Runtime访问到所有的变量、属性以及方法。

注意


  1. 使用@objc可以将Swift编写的类桥接至Objective-C,但是必须保证该类的基类为NSObject。
  2. 在基于Swift3以及之前版本的Swift语言项目中,当Swift编写的类被标记为@objc时,编译器会自动为该类中所有非private访问级别的成员默认添加@objc关键字。但是在Swift4之后,该功能被关闭,我们需要为我们想要桥接至Objective-C的类、属性、方法进行手动显式地添加@objc标记。

现在,我们再来讨论一下在Cocoa框架中使用Swift,首先我们来看一下Target-Action模式。

Target-Action模式

首先我们看一下Target-Action模式中的两个关键点:Target以及Action。

我们使用Timer的API来查看一下:

public init(timeInterval ti: TimeInterval, target aTarget: Any, selector aSelector: Selector, userInfo: Any?, repeats yesOrNo: Bool)

首先,target为Any类型,其余没有特殊要求。

其次,我们来重点探讨一下action。

Action

从API中可以看出,Action的类型为Selector。在官方文档中,Selector的描述如下:The Objective-C SEL type.

由此我们可以看出,该Selector必须是桥接到Objective-C的方法。

Selector的初始化方式有两种,其中有一些注意点如下:

1.#selector()

我们可以使用#selector()来初始化一个Selector。

#selector

由Xcode的提示我们可知,所需要使用到的方法也必须是经过@objc标记的方法。

2.init(_ str: String)

使用Selector的初始化方法来进行创建,通过传入一个方法名称的字符串来创建Selector。

在使用该方法创建时,同样需要注意使用到的方法也必须是进过@objc标识的方法。

同时,在使用该方法创建时,方法名称字符串必须是Objective-C语法类型的方法名。

例如,我们使用如下方法创建Selector:

@objc func repeatFunction(timer: Timer) -> Void {print(timer)
}

那么对应的方法应该是:

Selector("repeatFunctionWithTimer:")

接下里我们再看一下Cocoa中的另一个模式。

Key-Value Observing模式

首先我们查看一下与KVO相关的几个方法:

extension NSObject {open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
}extension NSObject {open func addObserver(_ observer: NSObject, forKeyPath keyPath: String, options: NSKeyValueObservingOptions = [], context: UnsafeMutableRawPointer?)@available(iOS 5.0, *)open func removeObserver(_ observer: NSObject, forKeyPath keyPath: String, context: UnsafeMutableRawPointer?)open func removeObserver(_ observer: NSObject, forKeyPath keyPath: String)
}

由方法定义我们可知,想要使用KVO,那么被观察的对象需要是NSObject的子类。那好,我们定义一个NSObject的子类:

class SwiftModel: NSObject {var name: String?
}

接下来我们创建一个该类的实例,并对该实例进行 name 变量的观察:

override func viewDidLoad() {super.viewDidLoad()let model = SwiftModel()model.addObserver(self, forKeyPath: "name", options: [.new], context: nil)model.name = "new name"model.removeObserver(self, forKeyPath: "name")}override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {if object is SwiftModel, keyPath == "name" {print(change!)}
}

接下来运行,我们发现并没有触发KVO观察的回调,这是为什么呢?

我们知道,KVO机制是在建立观察时动态的为被观察的类创建一个子类,同时重写被观察键的setter方法(关于KVO的探讨,可以查看该篇博客)。

此时我们没有触发KVO的观察回调,是由于@objc虽然将属性桥接至Objective-C上,但是Swift编译器还是无法实现动态调用,我们需要将被观察的属性设置为动态调用,即使用dynamic关键词来标识属性:

class SwiftModel: NSObject {@objc dynamic var name: String?
}

至此,我们实现了在Swift中使用KVO,下面是在使用KVO时的几点总结:

1.被观察的类需要继承自NSObject。

2.被观察的属性/变量需要制定动态调用,即使用dynamic来标识。

3.在使用dynamic标识时,需要将属性/变量桥接至Objective-C,即同时使用@objc dynamic标识。

其他一些相关注意事项

在实际开发中,我们需要对方法进行Swizzle,此时我们一般使用下面的方法:

func swizzle(instanceMethod: Selector, method: Selector) -> Void {let cls = type(of: self)let origMethodOptional = class_getInstanceMethod(cls, instanceMethod)let newMethodOptional = class_getInstanceMethod(cls, method)guard let origMethod = origMethodOptional, let newMethod = newMethodOptional else {return}if class_addMethod(cls,instanceMethod,method_getImplementation(newMethod),method_getTypeEncoding(newMethod)) {class_replaceMethod(cls,method,method_getImplementation(origMethod),method_getTypeEncoding(origMethod))} else {class_replaceMethod(cls,method,class_replaceMethod(cls,instanceMethod,method_getImplementation(newMethod),method_getTypeEncoding(newMethod))!,method_getTypeEncoding(origMethod))}
}

此时如果我们不对需要交换的方法进行特殊的处理,那么可能会造成交换失败,如下例:

class SuperClass: NSObject {@objc func sayHello() -> Void {print("Super Say Hello")}
}class SubClass: SuperClass {override init() {super.init()self.swizzle(instanceMethod: #selector(sayHello), method: #selector(hookSayHello))}@objc func hookSayHello() -> Void {print("Sub Say hello")}
}

接下来我们初始化一个SubClass对象,然后分别调用sayHello()和hookSayHello()方法,理论上两个方法进行了交换,打印结果也应该进行交换,但结果并没有交换。

这是为什么呢?

其实不难想象,KVO中要将观察的属性设置为dynamic,目的就是为了在调用时动态访问到重写之后的setter方法。而在此处,两个方法并没有指定dynamic,虽然两个方法进行了调换,但是在调用时并没有动态调用,而仅仅是直接访问,所以并没有达到我们想要的效果。

我们可以通过以下步骤进行验证:

1.验证方法是否被交换

我们改动一下Runtime交换方法的函数,在其中打印一下交换前与交换后的方法指向:

添加代码

然后我们运行代码,此时方法的打印结果没有交换,但是方法的指向发生了改变:

交换结果

2.验证单个dynamic标识效果

  • 我们为sayHello方法添加dynamic,运行代码,发现两次打印都是”Sub Say Hello”。
  • 我们为hookSayHello方法添加dynamic,允许代码,发现两次打印都是”Super Say Hello”。

不难理解,当其中一个方法被指定为dynamic时,当访问该方法时,会采用动态调用的方式,进而访问到调换之后的实现。而没有指定dynamic的方法,还是按照直接调用的方法,访问到它自己本身的实现上。

3.验证两个dynamic标识效果

当两个方法都被指定为dynamic时,打印结果发生了交换,证明方法调换成功。

接下来我们继续对方法调换进行探索,假设hookSayHello方法是在SubClass的Extension中,那么对于dynamic的使用有和不同:

class SuperClass: NSObject {@objc func sayHello() -> Void {print("Super Say Hello")}
}class SubClass: SuperClass {override init() {super.init()self.swizzle(instanceMethod: #selector(sayHello), method: #selector(hookSayHello))}
}extension SubClass {@objc func hookSayHello() -> Void {print("Sub Say hello")}
}

我们依旧使用上述步骤进行验证:

1.验证方法是否被交换

我们运行代码,此时方法的指向发生了变化,同时两次打印结果均为”Super Say Hello”。

交换结果

2.验证单个dynamic标识效果

  • 我们为sayHello方法添加dynamic,运行代码,发现两次分别是”Sub Say Hello”,”Super Say Hello”。
  • 我们为hookSayHello方法添加dynamic,允许代码,发现两次打印都是”Super Say Hello”。

3.验证两个dynamic标识效果

当两个方法都被指定为dynamic时,打印结果发生了交换,证明方法调换成功。

从上述结果,我们可以总结出,sayHello方法的表现与之前验证结果一直,而hookSayHello的表现无论有没有指定dynamic,都进行了动态调用,由此可见,通过Extension新增的方法,均表现为dynamic。

至此,我们可以进行一个总结:

1.Runtime可以将Swift中的@objc方法进行调换。

2.Swift调用方法时,并不依赖于Runtime,即使方法对应的IMP已经改变,但Swift依旧可以使用直接调用方式调用到原始的IMP。

3.可以使用dynamic关键字将Swift中的方法指定为动态调用,此时的Swift方法与Objective-C中方法表现一致,会受到Runtime的影响。

4.通过Extension新增的方法,默认为dynamic的,这些方法受Runtime影响。


推荐阅读
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • 本文详细介绍了GetModuleFileName函数的用法,该函数可以用于获取当前模块所在的路径,方便进行文件操作和读取配置信息。文章通过示例代码和详细的解释,帮助读者理解和使用该函数。同时,还提供了相关的API函数声明和说明。 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • Commit1ced2a7433ea8937a1b260ea65d708f32ca7c95eintroduceda+Clonetraitboundtom ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文详细介绍了在ASP.NET中获取插入记录的ID的几种方法,包括使用SCOPE_IDENTITY()和IDENT_CURRENT()函数,以及通过ExecuteReader方法执行SQL语句获取ID的步骤。同时,还提供了使用这些方法的示例代码和注意事项。对于需要获取表中最后一个插入操作所产生的ID或马上使用刚插入的新记录ID的开发者来说,本文提供了一些有用的技巧和建议。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • iOS Xcode汇编模式切换的方法介绍
    一、概念 1.汇编指令:模拟器上运行的是Intel指令,而真机上运行的是arm指令, 2.每条汇编指令的格式总是由: 操作码,操作 ... [详细]
  • IvebeentryingforadayortwototryandgetashadowtodrawinsidethetextofanNSTextField ... [详细]
  • PHP图片截取方法及应用实例
    本文介绍了使用PHP动态切割JPEG图片的方法,并提供了应用实例,包括截取视频图、提取文章内容中的图片地址、裁切图片等问题。详细介绍了相关的PHP函数和参数的使用,以及图片切割的具体步骤。同时,还提供了一些注意事项和优化建议。通过本文的学习,读者可以掌握PHP图片截取的技巧,实现自己的需求。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
author-avatar
施工的公司_534
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有