目录

代码技巧

1. GNU-C的赋值扩展

即使用({...})的形式。这种形式的语句可以类似很多脚本语言,在顺次执行之后,会将最后一次的表达式的值作为返回值。

注意:这个不是懒加载

1
2
3
4
5
RETURN_VALUE_RECEIVER = {(
     // do whatever you want
     ...
     RETURN_VALUE; // 返回值
)};

REMenu 这个开源库中就使用了这种语法,如下:

1
2
3
4
5
6
7
8
9
_titleLabel = ({
   UILabel *label = [[UILabel alloc] initWithFrame:titleFrame];
   label.isAccessibilityElement = NO;
   label.contentMode = UIViewContentModeCenter;
   label.textAlignment = (NSInteger)self.item.textAlignment == -1 ? self.menu.textAlignment : self.item.subtitleTextAlignment;
   label.backgroundColor = [UIColor clearColor];
   label.autoresizingMask = UIViewAutoresizingFlexibleWidth;
   label;
});

使用这种语法的其中一个优点是结构鲜明紧凑,而且由于不用担心块里面的变量名污染外面变量名的问题。

2. case语句中使用范围表达式

GCCC11标准的语法扩展

比如,case 1 ... 5 就表示值如果在 1~5 的范围内则满足条件。 这里,省略号 ... 就作为一个范围操作符,其左右两个操作数之间至少要用一个空白符进行分割,如果写成 1...5 这种形式会引发词法解析错误。范围操作符的操作数可以是任一整数类型,包括字符类型。 另外,范围操作符的做操作数的值应该小于或等于右操作数,否则该范围表达式就会是一个空条件范围,永远不成立。

 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
#include <stdio.h>

int main(int argc, const char * argv[]) {

    int a = 1; 
    const int c = 10;

    switch(a) {
        // 这条case语句是合法的,并且与case 1等效 
        case 1 ... 1:
            printf("a = %d\n", a);
            break;

        // 这条case语句中的范围操作符的左操作数⼤于右操作数, 
        // 因此它是⼀个空条件范围,这条case语句下的逻辑永远不会被执⾏ 
        case 2 ... 1:
            puts("Hello, world!"); 
            break;

        // 使⽤const修饰的对象也可作为范围操作符的操作数 
        case 8 ... c:
            puts("Wow!");
            break;

        default: 
            break;
    }

    char ch = 'A'; 
    switch(ch) {
        // 从'A'到'Z'的ASCII码范围 
        case 'A' ... 'Z':
            printf("The letter is: %c\n", ch);
            break;

        // 从'0'到'9'的ASCII码范围 
        case '0' ... '9':
            printf("The digit is: %c\n", ch);
            break;

        default:
            break;
    }
}

3. __auto_type

GCCC11标准的语法扩展

1
2
3
4
5
6
7
#if defined(__cplusplus)
#define var auto
#define let auto const
#else
#define var __auto_type
#define let const __auto_type
#endif

例如:

1
2
3
4
 let block = ^NSString *(NSString *name, NSUInteger age) {
     return [NSString stringWithFormat:@"%@ + %ld", name, age];
 };
 let result = block(@"foo", 100);  // no warning

4. 结构体的初始化

1
2
3
4
 // 不加(CGRect)强转也不会warning
 GRect rect1 = {1, 2, 3, 4};
 CGRect rect2 = {.origin.x=5, .size={10, 10}}; // {5, 0, 10, 10}
 CGRect rect3 = {1, 2}; // {1, 2, 0, 0}

5. 数组的下标初始化

1
2
3
4
5
6
7
 const int numbers[] = {
     [1] = 3,
     [2] = 2,
     [3] = 1,
     [5] = 12306
 };
 // {0, 3, 2, 1, 0, 12306}

这个特性可以用来做枚举值和字符串的映射

1
2
3
4
5
6
7
8
 typedef NS_ENUM(NSInteger, Type){
     Type1,
     Type2
 };
 const NSString *TypeNameMapping[] = {
     [Type1] = @"Type1",
     [Type2] = @"Type2"
 };

又如 UITableView+FDIndexPathHeightCache中的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
 // All methods that trigger height cache's invalidation
 SEL selectors[] = {
     @selector(reloadData),
     @selector(insertSections:withRowAnimation:),
     @selector(deleteSections:withRowAnimation:),
     @selector(reloadSections:withRowAnimation:),
     @selector(moveSection:toSection:),
     @selector(insertRowsAtIndexPaths:withRowAnimation:),
     @selector(deleteRowsAtIndexPaths:withRowAnimation:),
     @selector(reloadRowsAtIndexPaths:withRowAnimation:),
     @selector(moveRowAtIndexPath:toIndexPath:)
 };

 for (NSUInteger index = 0; index < sizeof(selectors) / sizeof(SEL); ++index) {
     SEL originalSelector = selectors[index];
     SEL swizzledSelector = NSSelectorFromString([@"fd_" stringByAppendingString:NSStringFromSelector(originalSelector)]);
     Method originalMethod = class_getInstanceMethod(self, originalSelector);
     Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
     method_exchangeImplementations(originalMethod, swizzledMethod);
 }

6. 自带提示的keypath

1
2
#define keypath2(OBJ, PATH) \
 (((void)(NO && ((void)OBJ.PATH, NO)), # PATH))

7. 逗号表达式

逗号表达式取后值,但前值的表达式参与运算,可用void忽略编译器警告

1
 int a = ((void)(1+2), 2); // a == 2

于是上面的keypath宏的输出结果是#PATH也就是一个c字符串

8. C函数重载标示符

RTRootNavigationController 中有用到这个技巧

1
2
3
4
5
6
7
 __attribute((overloadable)) NSInteger ZD_SumFunc(NSInteger a, NSInteger b) {
     return a + b;
 }

 __attribute((overloadable)) NSInteger ZD_SumFunc(NSInteger a, NSInteger b, NSInteger c) {
     return a + b + c;
 }

9. 参数个数

1
2
3
4
5
// 最多支持10个参数
#define COUNT_PARMS2(_a1, _a2, _a3, _a4, _a5, _a6, _a7, _a8, _a9, _a10, RESULT, ...) RESULT
#define COUNT_PARMS(...) COUNT_PARMS2(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

int count = COUNT_PARMS(1,2,3,4,5,6); // 预处理时count == 6

10. 同名全局变量或者全局函数共存

1
2
3
4
 // 下面二者可以并存
 NSDictionary *ZDInfoDict = nil;

 __attribute__((weak)) NSDictionary *ZDInfoDict = nil;

11. 偷梁换柱

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Test {
    dynamic func foo() {
        print("bar")
    }
}

extension Test {
    @_dynamicReplacement(for: foo())
    func new_foo() {
        print("bar new")
        foo()  // calls previous implementation
    }
}

Test().foo() // bar new

有2点需要说明:

  1. 标注dynamic关键字
  2. 在工程中运行,playground不支持

12. 移花接木

 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
@_silgen_name("backtrace")
internal func swift_backtrace(_ callstacks: UnsafeMutableRawPointer, _ counts: Int) -> Int

@_silgen_name("backtrace_symbols")
internal func swift_backtrace_symbols(_ callstacks: UnsafeRawPointer, _ counts: Int) -> UnsafeMutablePointer<UnsafePointer<CChar>>?

//------------------------------------------------

static func callstack() -> [String] {
    var callstack = [UnsafeMutableRawPointer?](repeating: nil, count: 128)
    let frames = swift_backtrace(&callstack, callstack.count)

    var callstackArr: [String] = []
    if let symbols = swift_backtrace_symbols(&callstack, frames) {
        for frame in 0..<frames {
            let symbol = String(cString: symbols[frame])
            callstackArr.append(symbol)
        }

        free(symbols)

        os_log("堆栈信息 => %@", log: .apmLog, type: .info, callstackArr)
    }

    return callstackArr
}

13. Swift类名解析

1
2
# xcrun swift-demangle _TtC11NewWolfKill22WKRoomControlMenuModel
$ xcrun swift-demangle <your-mangled-symbol>

14. 队列校验

1
2
3
4
5
6
7
methodThatCallsBackOnMain(completion: { result in
    // 确保在主队列中调用
    dispatchPrecondition(.onQueue(.main))

    // process `result`
    // ...
})

15. 指针调用Swift函数

https://github.com/apple/swift/issues/70630

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class Printable {
    public init (from sender : String) {
        print ("Hello from \(sender)!")
    }

    public init (from sender : String, as name : String = "Bastie") { // 🆘  in result of init with one parameter unusable default value "Bastie" from init method
        print ("Hello from \(name)?")
    }
}

let fn = Printable.init(from:as:)
let _ = fn("Sebastian")
// or
let _ = Printable.init(from:as:)("Sebastian")

16. 使用map简化代码

https://github.com/ReactiveX/RxSwift/pull/2549

重构前:

1
2
3
4
5
6
7
let disposable: Disposable

if let onDisposed = onDisposed {
    disposable = Disposables.create(with: onDisposed)
} else {
    disposable = Disposables.create()
}

重构后:

1
let disposable: Disposable = onDisposed.map( Disposables.create(with:) ) ?? Disposables.create()

17. Assert

assert会导致程序退出,下面这种方式不会使程序退出而只是让IDE断在指定位置,类似于打断点那种效果

1
2
// 适用于所有架构
__builtin_debugtrap()

如果是模拟器,可以使用内联汇编的方式

1
asm("int3")

如果是win平台,可以用

1
__debugbreak()

贴段样例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// MARK: - ZDAssert
#if DEBUG
#ifndef ZDAssert
#define ZDAssert(condition, format, ...) do { \
    _Pragma("clang diagnostic push") \
    _Pragma("clang diagnostic ignored \"-Wobjc-literal-conversion\"") \
    if (condition) break; \
    if (format) printf("\n%s\n\n", [[NSString stringWithFormat:format, ##__VA_ARGS__] UTF8String]); \
    _Pragma("clang diagnostic pop") \
    __builtin_debugtrap(); \
} while(0);
#endif
#else
#ifndef ZDAssert
#define ZDAssert(condition, format, ...)
#endif
#endif

这里建议加上一个条件–只在调试期间起作用,因为在非调试阶段执行到这个trap程序会挂掉,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import Darwin

// See http://developer.apple.com/library/mac/#qa/qa1361/_index.html
@objc public class func isDebuggerAttached() -> Bool {
    var info = kinfo_proc()
    var mib = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()]
    var size = MemoryLayout<kinfo_proc>.stride
    let junk = sysctl(&mib, u_int(mib.count), &info, &size, nil, 0)
    assert(junk == 0)
    let isDebuggerAttaced = info.kp_proc.p_flag & P_TRACED != 0
    return isDebuggerAttaced
}        

18. 保证对象的生命周期

  1. Swift

    withExtendedLifetime()

    1
    2
    3
    4
    5
    6
    
    var owningReference = Instance()
    ...
    withExtendedLifetime(owningReference) {
        dosomething(...)
    }  // Assuming: No stores to owned occur for the dynamic lifetime of
       //         the withExtendedLifetime invocation.
    
  2. Objective-C

    在 Objective-C ARC 中你可以使用 __attribute__((objc_precise_lifetime)) 或者 NS_VALID_UNTIL_END_OF_SCOPE 来标注变量以达到类似的效果。

19. 区间判断

    判断某一个值x是否在区间[min, max]内    

    

第一个 (x - minx) 如果 x < minx 的话,得到的结果 < 0 ,即高位为 1,第二个判断同理,如果超过范围,高位也为 1,两个条件进行比特或运算以后,只有两个高位都是 0 ,最终才为真

1
if (( (x - minx) | (maxx - x) ) >= 0) ...

20. 通过异或混淆key

通过异或的方式(字符串正常会进入常量区,但是通过异或的方式编译器会直接换算成异步结果)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#define ENCRYPT_KEY 0xAC

static NSString * AES_KEY(){
    unsigned char key[] = {
        (ENCRYPT_KEY ^ 'd'),
        (ENCRYPT_KEY ^ 'e'),
        (ENCRYPT_KEY ^ 'm'),
        (ENCRYPT_KEY ^ 'o'),
        (ENCRYPT_KEY ^ '_'),
        (ENCRYPT_KEY ^ 'A'),
        (ENCRYPT_KEY ^ 'E'),
        (ENCRYPT_KEY ^ 'S'),
        (ENCRYPT_KEY ^ '_'),
        (ENCRYPT_KEY ^ '\0'),
    };
    unsigned char * p = key;
    while (((*p) ^= ENCRYPT_KEY) != '\0') {
        p++;
    }
    return [NSString stringWithUTF8String:(const char *)key];
}

21. 另类的NSTimer破环方案

block结构中有个私有的函数:invoke

1
2
3
4
5
6
7
@implementation NSTimer (ZDUtility)

+ (instancetype)zd_fireSecondsFromNow:(NSTimeInterval)delay block:(dispatch_block_t)block {
    return [self scheduledTimerWithTimeInterval:delay target:block selector:@selector(invoke) userInfo:nil repeats:NO];
}

@end

参考