Skip to content

Latest commit

 

History

History
410 lines (305 loc) · 11.1 KB

Code Tips.md

File metadata and controls

410 lines (305 loc) · 11.1 KB

Code Tips

C && Objective-C代码技巧


1. GNU C的赋值扩展:

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

注意:这个不是懒加载

RETURN_VALUE_RECEIVER = {(
     // do whatever you want
     ...
     RETURN_VALUE; // 返回值
)};

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

_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 这种形式会引发词法解析错误。范围操作符的操作数可以是任一整数类型,包括字符类型。 另外,范围操作符的做操作数的值应该小于或等于右操作数,否则该范围表达式就会是一个空条件范围,永远不成立。

#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标准的语法扩展

#if defined(__cplusplus)
#define var auto
#define let auto const
#else
#define var __auto_type
#define let const __auto_type
#endif

例如:

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

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. 数组的下标初始化:

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

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

 typedef NS_ENUM(NSInteger, Type){
     Type1,
     Type2
 };
 const NSString *TypeNameMapping[] = {
     [Type1] = @"Type1",
     [Type2] = @"Type2"
 };

又如 UITableView+FDIndexPathHeightCache中的例子:

 // 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宏:

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

7. 逗号表达式:

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

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

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

8. C函数重载标示符:

RTRootNavigationController 中有用到这个技巧

 __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. 参数个数

// 最多支持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. 同名全局变量或者全局函数共存:

 // 下面二者可以并存
 NSDictionary *ZDInfoDict = nil;

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

11. 偷梁换柱:

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. 移花接木

@_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类名解析

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

14. 队列校验

methodThatCallsBackOnMain(completion: { result in
    // 确保在主队列中调用
    dispatchPrecondition(.onQueue(.main))

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

15. Assert

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

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

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

asm("int3")

如果是win平台,可以用

__debugbreak()

贴段样例:

// 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程序会挂掉,代码如下:

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
}        

16. 保证对象的生命周期

  1. Swift

    withExtendedLifetime()

    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 来标注变量以达到类似的效果。

17. 区间判断

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

    

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

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

参考: