iOS开发Swift

Swift小知识点之@objc

一 概述


  • 背景

Objective-C 对象是基于运行时的,方法或属性使用动态派发 ,在运行调用时再决定实际调用的具体实现。而 Swift 为了追求性能,如果没有特殊需要的话,是不会在运行时再来决定这些的。也就是说,Swift 类型的成员或者方法在编译时就已经决定,而运行时便不再需要经过一次查找,而可以直接使用。

Objective-C 中所有类都继承自NSObject,Swift 中的类如果要供 Objective-C 调用,必须也继承自NSObject。

  • 作用

@objc修饰符的根本目的是用来暴露接口给 Objective-C 的运行时(类、协议、属性和方法等)

添加@objc修饰符并不意味着这个方法或者属性会采用 Objective-C 的方式变成动态派发,Swift 依然可能会将其优化为静态调用

二 @objc


1、Swift 3.0

  • a. 编译器默认给继承于NSObject的类的所有方法都隐式添加@objc;缺点是大量@objc导致二进制文件增大;
class MyClass: NSObject {
  func print() { } // 包含隐式的 @objc
}
  • b. Swift在某些方面非常的随意亲切,比方说类名,Swift可以使用中文命名,但OC却只能使用ASCII码,在使用@objc的时候,需要指定OC中指定的ASCII码的名称,举个小例子如下。
@objc(MyClass)
class 我的类: NSObject {
@objc(greeting:)
  func 问候(名字: String) {
    print("你好 \(名字)")
  }
}

2、Swift 4.0

a. 隐式添加@objc只存在以下场景:覆盖父类的ObjC方法、符合一个ObjC的协议;

  • 覆盖父类的ObjC方法
class Person : NSObject {
    ///计算属性
    @objc var name:String {
        return "Swift"
    }

///存储属性
    @objc var sex:String = "男"
    
    ///方法
    @objc func greeting(){
        
    }
}

class Student:Person {
    override var name:String {
        return ""
    }
    
    //Cannot override with a stored property 'sex'
    //子类不能重写父类的存储属性
    //override var sex: String = "男"
    
    override func greeting() {
        
    }
}
  • 符合一个ObjC的协议
@objc protocol MyProtocol {
    func myFun()
}

class Person:MyProtocol {
    func myFun() {
        
    }
}
  • 当属性声明为@IBAction或者@IBOutlet
  • 当属性声明为@NSManaged
Core Data 提供了基本存储和实现NSManagedObject子类的一组属性。在与Core Data 模型中管理对象子类相关的特性或者关系的每个属性定义之前,将@NSmanaged特性加入。与 Objective-C 里面的 @dynamic特性类似,@NSManaged特性告知 Swift 编译器,这个属性的存储和实现将在运行时完成。但是,与@dynamic不同的是,@NSManaged特性仅在 Core Data 支持中可用。
  • @objcMembers 修饰的类,它和它子类的属性方法扩展都会隐式的添加上@objc

为什么要用这个关键字呢?

@objcMembers 在Swift 4中继承 NSObject 的 swift class 不再默认全部 bridge 到 OC,如果我们想要使用的话我们就需要在class前面加上@objcMembers 这么一个关键字。

swift3:在 swift 3 中除了手动添加 @objc 声明函数支持 OC 调用还有另外一种方式:继承 NSObject。class 继承了 NSObject 后,编译器就会默认给这个类中的所有函数都标记为 @objc ,支持 OC 调用。
swift4:苹果在Swift 4 中苹果修改了自动添加 @objc 的逻辑: 一个继承 NSObject 的 swift 类不再默认给所有函数添加 @objc。只在实现 OC 接口和重写 OC 方法时才自动给函数添加 @objc 标识。Swift4 后继承自NSObject的类不再隐式添加@objc关键字,但在某些情况下非常依赖 Objective-C 的运行时(如 XCTest),所以在 Swift4 中提供了@objcMembers关键字,对类和子类、扩展和子类扩展重新启用@objc推断。
  • 实例
@objcMembers
class MyClass : NSObject {
  func foo() { }             // implicitly @objc
    
  func bar() -> (Int, Int){  // not @objc, because tuple returns。aren't representable in Objective-C
        return (1,1)
  }
}
 
extension MyClass {
  func baz() { }      // implicitly @objc
}
 
class MySubClass : MyClass {
  func wibble() { }   // implicitly @objc
}
 
extension MySubClass {
  func wobble() { }   // implicitly @objc
}
  • 若扩展名前加@objc,则该扩展的所有方法都隐式添加@objc
class MyClass { }
@objc extension MyClass {
    func a() { } // 包含隐式的 @objc
}

b. 显式给方法添加@objc;

  • Selector 中调用的方法需要在方法前声明 @objc,目的是允许这个函数在“运行时”通过 Objective-C 的消息机制调用
verride func viewDidLoad() {
    super.viewDidLoad()
    let btn = UIButton(type: .contactAdd)     
    btn.addTarget(self, action: #selector(click), for: .touchUpInside)
}
@objc func click()  {      
    print("Button clicked")
}
  • 协议的方法可选时,协议和可选方法前要用 @objc声明
@objc protocol OptionalProtocol {
    @objc optional func optionalMethold1()
    @objc optional func optionalMethold2()
}
  • 用weak修饰协议时,协议前面要用@objc声明
@objc protocol MyDelegate{   
    func methold1()  
}

class MyClass{
    weak var delegate: MyDelegate?  
}

c 若扩展名前加@noobjc,则该扩展的所有方法都不会隐式添加@objc(排除类名前加@objcMembers的影响);

@objcMembers class MyClass : NSObject {
   func print() { } // 包含隐式的 @objc
}
@nonobjc extension MyClass {
   func a() { } // 不会包含隐式的 @objc
}

d.设置Xcode的Build Settings中的Swift 3 @objc Inference,决定是否继续采用Swift 3.0隐式添加@objc的模式;