Fork me on GitHub

Objective-C 与 Swift 方法的类比系列(一)

单例

Objective-C 写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@interface IHUserManager : NSObject
+ (instancetype)sharedInstance;
@end
@implementation IHUserManager
+ (instancetype)sharedInstance {
static IHUserManager *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}

@end

Swift 写法

1
2
3
4
import Foundation
class IHUserManager {
static let sharedInstance = IHUserManager()
}

block和闭包在对象绑定时的坑

分析

oc 中,使用 runtime 的对象绑定相信大家都使用过,而且在 block 上的使用也经常看到,在 oc 中可以把 block 直接使用 runtime 通过 key 和其他的对象绑定起来,但在 swift 中闭包不是:AnyObject 类型 使用在进行对象绑定时无法进行。
参考:
1.stackoverflow.com
2.http://nshipster.cn

需要使用:
public func unsafeBitCast<T, U>(x: T, _: U.Type) -> U
函数

代码实现

对 UIControl 增加 Block 属性和 Closure 属性

swift 具体的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typealias IHClosure = @convention(block)() -> ()

extension UIControl {

private struct ClosureKey {
static var touchUpInsideKey = "touchUpInsideKey"
}

func ih_addTouchUpInsideClosure(closure: IHClosure) {
let obj: AnyObject = unsafeBitCast(closure, AnyObject.self)
objc_setAssociatedObject(self, &ClosureKey.touchUpInsideKey, obj, .OBJC_ASSOCIATION_COPY_NONATOMIC)

self.addTarget(self, action: #selector(UIControl._touchUpInside), forControlEvents: .TouchUpInside)
}

@objc private func _touchUpInside() {
let obj: AnyObject = objc_getAssociatedObject(self, &ClosureKey.touchUpInsideKey)
let closure: IHClosure = unsafeBitCast(obj, IHClosure.self)
closure()
}
}

Objective-C 具体的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#import "UIControl+Block.h"
#import <objc/runtime.h>

const char kTouchUpInside = '\0';

@implementation UIControl (Block)

- (void)ih_addTouchUpInsideWithBlock:(dispatch_block_t)block {
objc_setAssociatedObject(self, @selector(_touchControlEvents), block, OBJC_ASSOCIATION_COPY_NONATOMIC);
[self addTarget:self action:@selector(_touchControlEvents) forControlEvents:UIControlEventTouchUpInside];
}

- (void)_touchControlEvents {
dispatch_block_t block = objc_getAssociatedObject(self, _cmd);
if (block) block();
}
@end

swift 和 oc 之 load方法不同以及处理方案

分析

在 oc 开发中,APP统计时,自然会使用到 方法的替换,下面方法

OBJC_EXPORT void method_exchangeImplementations(Method m1, Method m2) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);

通常会为 vc 添加一个 category 同时在 + load 方法里面的其做相应的处理。但在 swift 中系统不允许使用 + load 方法。那么我们可以想办法使用:
+ initialize 方法

代码实现

oc 具体的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#import "UIViewController+Swizzle.h"
#import <objc/runtime.h>

@implementation UIViewController (Swizzle)

+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{

// 获取系统的方法的 方法对象
Method orignViewDidLoad = class_getInstanceMethod([UIViewController class], @selector(viewDidLoad));

// 获取准备用来替换的 方法对象
Method replacingMethod = class_getInstanceMethod([UIViewController class], @selector(_viewDidLoad_Swizzling));


BOOL didAddMethod = class_addMethod([self class], @selector(viewDidLoad), method_getImplementation(replacingMethod), method_getTypeEncoding(replacingMethod));

if (didAddMethod) {
// 替换一下方法地址
class_replaceMethod([self class], @selector(_viewDidLoad_Swizzling), method_getImplementation(orignViewDidLoad), method_getTypeEncoding(orignViewDidLoad));
}else{
// 直接交换的调用
method_exchangeImplementations(orignViewDidLoad, replacingMethod);
}
});
}

+ (void)_viewDidLoad_Swizzling {
[self _viewDidLoad_Swizzling];
}
@end

swift 具体的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
extension UIViewController {

public override class func initialize() {

struct Static {
static var token: dispatch_once_t = 0
}

if self !== UIViewController.self {
return
}

dispatch_once(&Static.token) {
let originalSelector = #selector(UIViewController.viewWillAppear(_:))
let swizzledSelector = #selector(UIViewController.nsh_viewWillAppear(_:))

let originalMethod = class_getInstanceMethod(self, originalSelector)
let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)

let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
if didAddMethod {
class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
}

func nsh_viewWillAppear(animated: Bool) {
self.nsh_viewWillAppear(animated)
}
}

swift 和 oc 之 Log 处理

  • 在 oc 中为: NSLog
  • 在 swift 中为: print

我们在平常的开发中肯定会对 oc 的 NSLog 做一个特殊处理,打印更多的信息,在上线以后关闭日志输出,最简单的实现方式如下:

1
2
3
4
5
#ifdef DEBUG
#define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#else
#define DLog(...)
#endif

但是在 swift 中没有宏定义,使用也没 oc中的 DEBUG.

简单的代码如下:

1
2
3
4
5
func IHLog<T>(message: T ,
file: String = (#file as NSString).lastPathComponent,
funcName: String = #function, linNUm: Int = #line) -> () {
print("\(file) [\(funcName)] \(linNUm) : \(message)")
}

添加环境控制:

第1步

第2步.png

第3步.png

优化的代码如下:

1
2
3
4
5
6
7
 func IHLog<T>(message: T ,
file: String = (#file as NSString).lastPathComponent,
funcName: String = #function, linNUm: Int = #line) -> () {
#if IS_DEBUG
print("\(file) [\(funcName)] \(linNUm) : \(message)")
#endif
}

PS: IS_DEBUG 是你随便取的哦 在添加的地方必须为: -D+名称,这里我取为 IS_DEBUG

大功告成,release 环境下不会有 log 信息了、O(∩_∩)O哈哈~

Github源码


交换2个值

OC使用的方法就不描述了

Swift 使用元组

1
2
3
var obj1 = "1"
var obj2 = "2"
(obj1, obj2) = (obj2, obj1)

GCD延时函数

1
2
3
4
func delay(_ delay:Double, closure:@escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
1
2
3
delay(1) {
//...
}

SEL

1
2
3
#selector(UIViewController.present(_:animated:completion:))
let vc = UIViewController()
#selector(vc.self.present(_:animated:completion:))

获取类 class

1
2
self.class
self.self

是否属于一个类型

1
2
3
4
5
6
7
8
9
// 是否属于一个类型
if self is UIViewController {
}
let obj = NSObject()
if obj is [String] {
print("c")
}
if obj is [Any] {
}

copy mutableCopy

1
2
3
4
5
6
// copy mutableCopy
var arr = [String]()
let arr1 = (arr as NSArray).mutableCopy()
let arr2 = (arr as NSArray).copy()
arr = arr1 as! Array<String>
arr = arr2 as! Array<String>

for in

1
2
3
4
5
6
7
8
9

for i in 1...2 {}
for i in 1..<2 {}
for obj in array{}


for (i, obj) in (array?.enumerated())! {
array?[i] = (obj as! NSArray).mutableCopy() as! [Any]
}

属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
class Person: NSObject {
var name = ""
}

class BMPerson: Person {

/// 存储属性
var name1 = ""
var name2: String = ""
var name3: String? = ""
var name4: String?

/// 懒加载,swift lazy 和 oc懒加载不同,swift的懒加载永远只会走一次,不是和oc一样做非nil 判断
lazy var name6 = "c"
lazy var name7: String = "c"
lazy var view1: UIView = {
let view = UIView()
view.frame = CGRect.init(x: 0, y: 0, width: 100, height: 100)
view.backgroundColor = .red
return view
}()

/// 计算属性,不会生成功成员变量,等于oc中重写了set get 方法
var name8: String? {
get {
print("get")
return ""
}
set {
print("set")
}
}

/// 重写set get 方法 并且增加私有的成员变量
private var _name9: String?
var name9: String? {
get {
print("get")
return _name9
}
set {
print("set")
_name9 = newValue
}
}

var name10: String? {
willSet {
print("get")
}
didSet {
print("set")
}
}

var name11: String? {
willSet {
print("get")
}
}

/// 初始化方法对属性的设定,以及在 willSet 和 didSet 中对属性的再次设定都不会再次触发属性观察的调用,一般来说这会是你所需要的行为,可以放心使用能够。
var name12: String? {
didSet {
print("set")
var FA = false
FA = arc4random() % 2 == 0
if FA {
print("NO")
name12 = "CC"
}
}
}

/// 监控分类继承的属性变化
override var name: String {
willSet {
print("分类的属性 将要修改")
}
didSet {
print("分类的属性 将已经修改")
}
}

/// 只读属性
private var _name8 : String?
var name8: String? {
get {
return _name8
}
}
}

参考资料

- END -
关注微信公众号,发现更多精彩

文章作者:梁大红

特别声明:若无特殊声明均为原创,转载请注明,侵权请联系

版权声明:署名-非商业性使用-禁止演绎 4.0 国际