iOS 中那些不常用的关键字

1,672 阅读7分钟

一. Swift

defer

在离开当前作用域之前, 执行 defer 内的代码. 如果有多个 defer 的话, 由下往上, 由外往内执行.

func test {
	defer {
            print("second")
            defer {
                print("third")
            }
        }
	defer {
            print("first")
        }
}

@testable

可测试性, 改变文件的访问级别, 如单元测试访问 Pods Project 中的模块时, 可以使用 @testable import Module 访问模块中 internal 级别的代码.

@testable import FooModule

@convention

修饰闭包, 使之满足不同场景的需求:

@convention(swift) , @convention(block),@convention(c)

let methond = class_getInstanceMethod(Animal.self, #selector(Animal.eat(food:)))
let imp = method_getImplementation(methond!)
// ,使用 @convention(c) 来声明兼容 c 的闭包
typealias Imp  = @convention(c) (Animal, Selector, String) -> Void
// 将原函数指针强转为带对应参数的指针
let castImp = unsafeBitCast(imp, to: Imp.self)

class

修饰协议, 修饰后的协议只可被类遵守.

protocol Renderable: class {}

associatedtype

关联类型, 类似于类和结构体的反省, 但由于我们不会遵守一个 protocol\<A\>, 又去遵守一个 protocol\<B\> 协议, 所以在语义上区分开, 使用了关联类型.

protocol Renderable: class {
    associatedtype Shape
    func draw(with shape: Shape)
}

fallthrough

用于 switch 中, 不同 case 的贯穿效果. 即上面的 case 执行完后, 如果有 fallthrough 修饰, 则继续执行下一个 case.

var age = 5
switch age {
case 0...5:
	print("baby")
	fallthrough
case 5..<18:
	print("teenager")
case 18...100:
	print("adult")
default:
	break
}
// log
// baby
// teenager

where

泛型约束

func eat<T>(food: T) where T: Eatable {}

Self

在协议中用于指代遵守该协议的类. 如在协议扩展中, 使用 Self.self 输出类名.

protocol Eatable {
    var name: String { get }
}

extension Eatable {
    var name: String {
        return String(describing: Self.self)
    }
}

Type

类类型: 作为方法声明的形参或属性的参数类型.

func test(type: UIView.Type) {}

var test: UIView.Type = UIView.self

throw, throws

抛出一个异常, 可以使用 do-try-catch 捕获.

func sendMessage(_ message: String, to phone: Int) throws -> Void {
	guard message.count > 0 else {
		throw NSError(domain: "Illegal Message", code: 0, userInfo: nil)
	}
	// ...
}

rethrows

抛出传入的闭包中的异常, 即传入的闭包抛出了异常, 则函数会重新将异常抛出到上一层.

func help(_ action: ((String, Int) throws -> Void)) rethrows -> Void {
	try action("", 110)
}
do {
	try help(self.sendMessage(_:to:))
	}
catch {
	// 接受到由 sendMessage throws-> help rethrows-> 的异常
	print(error)
}

operator

自定义操作符

prefix operator ++

prefix

用于自定义前置操作符声明, 或前置操作符方法的声明. 文档

prefix operator ~+
prefix func ~+ (good: String) -> String {
    return good + " Copyright © 2017 ABC"
}

infix

自定义中置操作符

infix operator ~:AdditionPrecedence
func ~ (left: String, right: String) -> String {
    return left + ":" + right
}

postfix

自定义后置操作符

postfix operator ~-
postfix func ~- (content: String) -> String {
    guard content.count > 2 else { return content }
    return content.substring(from: String.Index(encodedOffset: 2))
}

asociativity, precedence

之前版本用于自定义操作符的结合性声明, 目前版本已无效, 使用 precedence group .

infix operator ~:AdditionPrecedence
func ~ (left: String, right: String) -> String {
    return left + " " + right
}

left

之前版本用于自定义操作符的结合方向

right

之前版本用于自定义操作符的结合方向

convenience

便利构造器, 对自己的指定构造器的封装, 必须在实现内调用自己的指定构造器.

convenience init() {
    self.init(frame: CGRect.zero)
}
// 指定构造器
override init(frame: CGRect) {
    super.init(frame: frame)
}

dynamic

runtime 相关修饰符. 实际使用中需要多加留心.

  • Objective-C 源项目中, 继承于 Objective-C 的 Swift 类将会全部注册到 runtime, 即所有的属性都会默认生成 setter 和 getter, 同时自定义的方法也会出现在 runtime 中.
  • Swift 源项目中, 由于编译器偏向于将所有 Swift Class 优化为函数表派发(类比 C++的 vtable), 所以如果想在 Objective-C 代码中访问到属性或方法, 必须在 Swift 属性或方法前添加 dynamic 修饰, 注册到 runtime (仅仅由 @objc 修饰的方法有可能会被编译器优化成函数表派发).
/// Objective-C 源项目
class SomeClass {
  var name: String = "Scy" 
  func test() {}
}
/// Swift 源项目中, 需要使用 dynamic 注册到 runtime, 才可以使用动态派发访问.
class SomeClass {
  @objc dynamic var name: String = "Scy" 
  @objc dynamic func test() {}
}

indirect

枚举中的递归引用, 即在枚举中使用自身枚举类型.

indirect enum Animal {
    case Human(eat: Animal)
    case bird(eat: Worm)
}
struct Worm {
    var food = ["bugA", "bugB"]
}

mutating

修饰方法, 用于在 Struct, Enum, Protocol 中用于改变自己的值. 需要注意的是在协议中的使用:

如果协议被结构体(枚举)实现, 且结构体需要改变自己的值, 则必须要在协议方法前添加 mutating

protocol Renameable {
    var name: String { set get }
    mutating func rename()
}

struct Dog: Renameable {
    var name: String
	func rename() {
        name = "happy" + name
    }
}

nonmutating

修饰方法, 用于禁止 Struct, Enum, Protocol 中的方法改变自己的值, 不添加修饰符时的默认值.

protocol Renameable {
    var name: String { set get }
    nonmutating func rename()
}

#available

修饰类或方法, 用于条件编译.

#colorLiteral

编码时, 使用这个关键字可以直接弹出自定义的颜色选择器.

#imageLiteral

编码时, 使用这个关键字可以直接弹出 assets 内的图片集选择器.

#column

同Objective-C 中的宏 __column__, 输出当前列数.

#file

同Objective-C 中的宏 __file__, 输出当前文件名.

#function

同Objective-C 中的宏 __function__, 输出当前方法名.

#line

同Objective-C 中的宏 __line__, 输出当前行数.

#sourceLocation

用于修改 #file#line 的值, 用于调试.

#sourceLocation(file: "DebugInfo", line: 100)
// 此范围内的代码, 输出 #file = "DebugInfo", #line = 100
#sourceLocation()
// 恢复正常输出

二. Objective-C

__packed

可以使得变量或者结构体成员使用最小的对齐方式, 在 c 语言结构体中比较常用.

struct Person {
  int age;
} __attribute__ ((__packed__));

struct __packed Person;

goto

已经被历史所抛弃, 但是 Swift 使用 break, continue 跳转语句部分实现了这个功能.

loop: if (n<200)
{
	n++;
&emsp;&emsp;goto loop;
}

Nil

用于表示类对象为空, 同 nil, 只是语义上的一个区分.

// Nil = nil = ((void *)0)
Class FooClass = Nil;

voletile

直接存取原始内存地址, 忽略编译器优化. 防止在多线程时, 读取寄存器缓存, 造成数据不同步的错误.

void cFunction(volatile int *ptr) {}

@interface FooClass : NSObject
{
    volatile int *_ptr;
}
@property (nonatomic) volatile int pointee;
@end

assign/strong, readwrite, nonable, atomic

未添加修饰符时, 默认的修饰符.

@property NSString *name;

atomic

默认实现带 @synthesized(self) 的 setter 和 getter, 保证相对的线程安全, 因为通过直接访问实例变量还是会出现线程竞态. Swift 中的属性引入 Objective-C, 默认为 nonatomic.

@interface FooClass()
@property (atomic) NSString *property;
@end
// 相当于为 setter 和 getter 添加了锁. 但是通过
@impletation FooClass
@synthesize property = _property;

- (void)setProperty:(NSString *)property {
    @synchronized(self) {
        if (_property != property) {
            [_property release];
            _property = [property retain];
        }
    }
}

- (NSString *)property {
    @synchronized(self) {
        return _property;
    }
}
@end

nonable, nonnull

修饰 @property, 与 Swift 同期出现的, 用于 Objective-C 编译期检测指针是否可为空, 并抛出一个 warning. 混编时对应 Swift 中的可选值和非可选值.

事实上这些 null 相关的修饰主要用于桥接到 Swift, 在 Objective-C 中更多是为了约束编码者.

@property (nullable) NSString *firstName;
@property (nonnull) NSString *lastName;

_Nullable, _Nonnull, __nonnull, __nullable

修饰参数和返回值, Objective-C 中用于约束参数传递, 错误时将抛出一个警告. 混编时对应 Swift 中的可选值和非可选值.

- (NSString *_Nullable)hello;
- (NSString *_Nonnull)hi;

null_resetable

修饰@property, 值绝对不能为空, 但是 set 可以为空, 即[obj setProperty:nil] 是合法的, 只是需要重写 setter 方法, 处理空的情况.

@interface FooClass : NSObject
@property (null_resettable) NSString *name;
@end
  
@impletation FooClass
- (void)setName:(NSString *)name {
	if (name == nil) {
        // ....
    }
}
@end

null_unspecified

修饰 @property, 用于提醒编码者该属性有可能为空. 在 Swift 中使用null_unspecified 修饰的属性时, 属性为 Swift 中的可选值.

@property (null_unspecified) NSString *name;

@synthesize

用于合成成员变量, setter 和 getter.

@implementation FooClass
@synthesize property = _property;
@end

@dynamic

编译时不自动生成成员变量, setter 和 getter. 如 CoreData 中的 NSManagedObject, 在运行时动态创建setter 和 getter,这就是所谓的动态绑定.

@implementation FooClass
@dynamic property;
@end

@available

编译指令, 一般用于版本约束.

if (@available(iOS 9, *)) {
	// code for iOS 9
}

@encode

给定一个类型, 输出一位或多位的编码, 在方法签名时常用, 这里有对照表.

@encode(type)

@package

访问权限修饰符, 分三种情况.

32bit compiler 下 为 @public

64bit 下, 同一个 framework 内为 @public

64bit 下, 不同 framework 为 @private

@interface NSURLResponse : NSObject <NSSecureCoding, NSCopying>
{
    @package
    NSURLResponseInternal *_internal;
}