iOS App啟動優(yōu)化《Clang插樁》

前言

iOS App啟動優(yōu)化《二進(jìn)制重排》我們講述了App的pre-main階段的流程以及二進(jìn)制重排的原理,接著我們就用這篇文章來實(shí)現(xiàn)二進(jìn)制重排。

1 配置Clang插樁

我們打開Clang的官方文檔# Clang 13 documentation
在這里有一個Tracing PCs,PC指的是PC寄存器,CPU在讀取代碼的指針即讀取虛擬內(nèi)存的那一行代碼。
所以Tracing PCs跟蹤的是CPU執(zhí)行到的代碼。
如何使用的呢,官方文檔有詳細(xì)介紹,我們來配置下,我們先添加一個標(biāo)記

-fsanitize-coverage=trace-pc-guard 

如圖


1

我們編譯一下,如圖


2

這里報鏈接錯誤,找不到符號,這是因為還需要實(shí)現(xiàn)兩個回調(diào)函數(shù)
void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
                                                    uint32_t *stop) {
  static uint64_t N;  // Counter for the guards.
  if (start == stop || *start) return;  // Initialize only once.
  printf("INIT: %p %p\n", start, stop);
  for (uint32_t *x = start; x < stop; x++)
    *x = ++N;  // Guards should start from 1.
}

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
  if (!*guard) return;
  void *PC = __builtin_return_address(0);
  char PcDescr[1024];
  printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
}

我們再次編譯,編譯成功。
這里有一行printf("INIT: %p %p\n", start, stop);代碼,我們看下這個start和stop是什么,我們運(yùn)行一下,看下效果,如圖

3

這里打了兩個地址,我們來分析下這些是什么。
startstop都是uint32_tunsigned int類型的指針,這說明上面打印的地址存放的是unsigned int類型數(shù)據(jù),這些unsigned int類型數(shù)據(jù)到底是什么,我們看下,如圖
4

這里的startstop代表符號個數(shù),我們看下最后個數(shù)據(jù),如圖
5

其中11000000是最后一個數(shù)據(jù),stop往上走4個字節(jié)讀取最后一個數(shù)據(jù)。
*for (uint32_t *x = start; x < stop; x++)
x = ++N;
這里就是從start位置到stop這個位置讀取符號個數(shù),這里是11個符號,我們來驗證下

void test() {
    
}

我們在ViewController.m加入

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
}

再次運(yùn)行,如下所示


6

這里變成了13,我們剛才增加了兩個方法,加進(jìn)去了,這說明我們的方法和函數(shù)都攔截到了。
我們再來嘗試下block,如下

void (^block)(void) = ^(void) {
    
};

我們再來看,如圖


7

這說明我們的block也攔截住了。
這個Clang的Trace是全局的,其它的文件一樣可以攔截。

+ (void)load {
    
}

+ (void)initialize {
    
}

我們再加上這兩個函數(shù),調(diào)試如下圖

8

說明loadinitialize方法可以攔住的。
__sanitizer_cov_trace_pc_guard我們來調(diào)試下這個函數(shù),打個斷點(diǎn),如圖
9

運(yùn)行項目,點(diǎn)擊屏幕,看下堆棧,如圖
10

我們可以看到touchesBegan這個方法調(diào)起了__sanitizer_cov_trace_pc_guard這個函數(shù),我們改下touchesBegan的代碼,如下

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"屏幕點(diǎn)擊了");
}

再次運(yùn)行,如圖


11

經(jīng)過測試發(fā)現(xiàn),__sanitizer_cov_trace_pc_guard這個函數(shù)在NSLog之前調(diào)用了,我們在block,test()函數(shù)加上打印,發(fā)現(xiàn)都在NSLog之前調(diào)用了__sanitizer_cov_trace_pc_guard這個函數(shù),說明它可以攔截當(dāng)前項目中的所有符號,系統(tǒng)庫和三方庫不會攔截。
我們重新排列的是代碼的實(shí)現(xiàn)的二進(jìn)制,系統(tǒng)庫和三方庫不在我們
項目中生成Mach-o文件。
我們自定義的屬性生成的get和set方法是可以攔截到的。

2 Clang的原理分析

__sanitizer_cov_trace_pc_guard這個函數(shù)是HOOK一切的回調(diào)函數(shù),我們來分析下它是如何做到的。
我們在這個函數(shù)打個斷點(diǎn),然后運(yùn)行,過掉所有斷點(diǎn),再次打上斷點(diǎn),點(diǎn)擊屏幕,斷住之后,我們來分析下它的匯編,如圖

12

我們看下touchBegan,如圖
13

這里可以看出當(dāng)touchBegan被調(diào)起的時候,立馬進(jìn)入了__sanitizer_cov_trace_pc_guard這個回調(diào)函數(shù),
我們看到匯編bl 0x1000359e4 ; __sanitizer_cov_trace_pc_guard at ClangTrace.m:29,這是bl到了__sanitizer_cov_trace_pc_guard這個函數(shù),這說明我們只要添加了Clang插樁的標(biāo)記,編譯器就會在所有的方法,函數(shù),block的代碼實(shí)現(xiàn)的邊緣的加上一句bl 0x1000359e4 ; __sanitizer_cov_trace_pc_guard代碼,在實(shí)現(xiàn)函數(shù)的代碼之前加上了這句代碼,同樣在函數(shù)和block中都有這樣的代碼。
這里相當(dāng)于修改了二進(jìn)制文件,如何修改的,我們分析下。
在所有的方法前面插入一行代碼,只有編譯器能做到,編譯器在讀到我們的方法,函數(shù),block時就會插入這行代碼。
我們是通過Other c Flags 添加的標(biāo)記,所以肯定是在編譯期做這個插入代碼的動作。

3 獲取到符號

我們的目標(biāo)是最終要生成order文件,那就需要獲取到符號名稱和順序,怎么才能獲取到呢,我們分析看看。
我們先打開void *PC = __builtin_return_address(0);這個函數(shù),__builtin_return_address這個函數(shù)返回的是上一個函數(shù)的地址,也就是調(diào)用者,這個PC就是上一個函數(shù)的地址,也就是函數(shù)的第一行代碼的地址,第0行插入了bl 0x1000359e4 ; __sanitizer_cov_trace_pc_guard at ClangTrace.m:29代碼
我們可以通過個地址獲取到符號的名字,代碼如下

 Dl_info info;
 dladdr(PC, &info);

這里需要導(dǎo)入#import <dlfcn.h>這個頭文件,函數(shù)的信息會存在info這個結(jié)構(gòu)體中,我們看下這個結(jié)構(gòu)體的定義,如下

/*
 * Structure filled in by dladdr().
 */
typedef struct dl_info {
        const char      *dli_fname;     /* Pathname of shared object */
        void            *dli_fbase;     /* Base address of shared object */
        const char      *dli_sname;     /* Name of nearest symbol */
        void            *dli_saddr;     /* Address of nearest symbol */
} Dl_info;
  • dli_fname m文件名字
  • dli_fbase m文件的地址
  • dli_sname 函數(shù)或方法的名字
  • dli_saddr 地址

我們來驗證下,代碼如下

printf("fname:%s\nfbase:%p\nsname:%s\nsaddr:%p\n",info.dli_fname,info.dli_fbase,info.dli_sname, info.dli_saddr);

運(yùn)行,效果如下

fname:/var/mobile/Containers/Bundle/Application/CDD47581-31B2-48C9-930D-B6D683798DB3/ClangTrace.app/ClangTrace
fbase:0x100024000
sname:+[ClangTrace load]
saddr:0x100029a30
INIT: 0x10002d788 0x10002d7e0
fname:/var/mobile/Containers/Bundle/Application/CDD47581-31B2-48C9-930D-B6D683798DB3/ClangTrace.app/ClangTrace
fbase:0x100024000
sname:main
saddr:0x100029e5c
fname:/var/mobile/Containers/Bundle/Application/CDD47581-31B2-48C9-930D-B6D683798DB3/ClangTrace.app/ClangTrace
fbase:0x100024000
sname:-[AppDelegate window]
saddr:0x100029d2c
fname:/var/mobile/Containers/Bundle/Application/CDD47581-31B2-48C9-930D-B6D683798DB3/ClangTrace.app/ClangTrace
fbase:0x100024000
sname:-[AppDelegate setWindow:]
saddr:0x100029d7c
fname:/var/mobile/Containers/Bundle/Application/CDD47581-31B2-48C9-930D-B6D683798DB3/ClangTrace.app/ClangTrace
fbase:0x100024000
sname:-[AppDelegate application:didFinishLaunchingWithOptions:]
saddr:0x100029ad0
fname:/var/mobile/Containers/Bundle/Application/CDD47581-31B2-48C9-930D-B6D683798DB3/ClangTrace.app/ClangTrace
fbase:0x100024000
sname:-[AppDelegate window]
saddr:0x100029d2c
fname:/var/mobile/Containers/Bundle/Application/CDD47581-31B2-48C9-930D-B6D683798DB3/ClangTrace.app/ClangTrace
fbase:0x100024000
sname:-[AppDelegate window]
saddr:0x100029d2c
fname:/var/mobile/Containers/Bundle/Application/CDD47581-31B2-48C9-930D-B6D683798DB3/ClangTrace.app/ClangTrace
fbase:0x100024000
sname:-[ViewController viewDidLoad]
saddr:0x1000297a4
fname:/var/mobile/Containers/Bundle/Application/CDD47581-31B2-48C9-930D-B6D683798DB3/ClangTrace.app/ClangTrace
fbase:0x100024000
sname:-[AppDelegate window]
saddr:0x100029d2c

這里就獲取了符號名稱以及調(diào)用順序。

4 利用原子隊列保存符號

我們在__sanitizer_cov_trace_pc_guard加入

NSLog(@"%@", [NSThread currentThread]);

然后順ViewController.m文件加入代碼

+ (void)load {
    
}

+ (void)initialize {
    
}

void test() {
    NSLog(@"test函數(shù)執(zhí)行");
    
}

void (^block)(void) = ^(void) {
    NSLog(@"block函數(shù)執(zhí)行");
};

- (void)sleepT{
    sleep(3);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    [self performSelectorInBackground:@selector(sleepT) withObject:nil];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//    NSLog(@"屏幕點(diǎn)擊了");
    test();
}

運(yùn)行項目,看下效果,如下所示

2021-09-02 13:40:26.424 ClangTrace[2817:871645] <NSThread: 0x12ee2be50>{number = 1, name = main}
+[ViewController load]
2021-09-02 13:40:26.425 ClangTrace[2817:871645] <NSThread: 0x12ee2be50>{number = 1, name = main}
main
2021-09-02 13:40:27.311 ClangTrace[2817:871645] <NSThread: 0x12ee2be50>{number = 1, name = main}
+[ViewController initialize]
2021-09-02 13:40:27.312 ClangTrace[2817:871645] <NSThread: 0x12ee2be50>{number = 1, name = main}
-[AppDelegate window]
2021-09-02 13:40:27.318 ClangTrace[2817:871645] <NSThread: 0x12ee2be50>{number = 1, name = main}
-[AppDelegate setWindow:]
2021-09-02 13:40:27.324 ClangTrace[2817:871645] <NSThread: 0x12ee2be50>{number = 1, name = main}
-[AppDelegate application:didFinishLaunchingWithOptions:]
2021-09-02 13:40:27.324 ClangTrace[2817:871645] <NSThread: 0x12ee2be50>{number = 1, name = main}
-[AppDelegate window]
2021-09-02 13:40:27.325 ClangTrace[2817:871645] <NSThread: 0x12ee2be50>{number = 1, name = main}
-[AppDelegate window]
2021-09-02 13:40:27.330 ClangTrace[2817:871645] <NSThread: 0x12ee2be50>{number = 1, name = main}
-[ViewController viewDidLoad]
2021-09-02 13:40:27.336 ClangTrace[2817:871748] <NSThread: 0x12ed20eb0>{number = 2, name = (null)}
-[ViewController sleepT]
2021-09-02 13:40:27.346 ClangTrace[2817:871645] <NSThread: 0x12ee2be50>{number = 1, name = main}
-[AppDelegate window]

這里打印了線程的信息,其中<NSThread: 0x12ed20eb0>{number = 2,name = (null)}這里說明了在子線程也是可以獲取的。
所以__sanitizer_cov_trace_pc_guard這個回調(diào)也是多線程的,我們的方法在子線程執(zhí)行的話,這個回調(diào)函數(shù)也是在子線程執(zhí)行的。在這里存儲數(shù)據(jù)的話,就有多線程的訪問,就會造成線程不安全。
我們就用線程安全的隊列OSAtomic來處理,代碼如下

// 定義原子隊列
static OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;

// 定義符號的結(jié)構(gòu)
typedef struct {
    void * pc; // 函數(shù)地址
    void * next; // 下一個函數(shù)節(jié)點(diǎn)
}SymboNode;

我們在修改__sanitizer_cov_trace_pc_guard這個函數(shù)的代碼,如下

/// HOOK一切的回調(diào)函數(shù)
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
    void *PC = __builtin_return_address(0);
    // 創(chuàng)建結(jié)構(gòu)體
    SymboNode *node = malloc(sizeof(SymboNode));
    // 先給node賦值,下個節(jié)點(diǎn)暫時先為空
    *node = (SymboNode){PC, NULL};
    // 結(jié)構(gòu)體入棧,node存入symbolList,并把下一個地址給到node的next屬性
    OSAtomicEnqueue(&symbolList, node, offsetof(SymboNode, next));
}

我們在touchesBegan加入代碼

 while (YES) {
        SymboNode *node =OSAtomicDequeue(&symbolList, offsetof(SymboNode, next));
        if (node == NULL) {
            break;
        }
        //獲取符號信息
        Dl_info info;
        dladdr(node->pc, &info);
        printf("%s\n",info.dli_sname);

    }

運(yùn)行看下效果,如下

[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]

發(fā)現(xiàn)死循環(huán)了,這是為什么呢?
__sanitizer_cov_trace_pc_guard這個函數(shù)把我們的循環(huán)也給攔截了,如何解決呢,我們需要修改Other C Flags的標(biāo)記,如下

-fsanitize-coverage=func,trace-pc-guard

我們再運(yùn)行一下項目看下結(jié)果,如下

-[ViewController touchesBegan:withEvent:]
-[SceneDelegate sceneDidBecomeActive:]
-[SceneDelegate sceneWillEnterForeground:]
-[ViewController sleepT]
-[ViewController viewDidLoad]
-[SceneDelegate window]
-[SceneDelegate window]
-[SceneDelegate window]
-[SceneDelegate scene:willConnectToSession:options:]
-[SceneDelegate window]
-[SceneDelegate window]
-[SceneDelegate setWindow:]
-[SceneDelegate window]
+[ViewController initialize]
-[AppDelegate application:didFinishLaunchingWithOptions:]
main
+[ViewController load]

結(jié)果正常了,所以我們應(yīng)該只攔截方法。

5 方法順序調(diào)整和去除重復(fù)的符號

從上面的運(yùn)行結(jié)果可以看出,這個順序是反的,并且這里還有很多重復(fù),需要我們處理一下,代碼如下

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 定義數(shù)組
    NSMutableArray<NSString *> *sybleNames = [NSMutableArray array];
    
    while (YES) {
        SymboNode *node =OSAtomicDequeue(&symbolList, offsetof(SymboNode, next));
        if (node == NULL) {
            break;
        }
        //獲取符號信息
        Dl_info info;
        dladdr(node->pc, &info);
        // 轉(zhuǎn)字符串
        NSString *name = @(info.dli_sname);
        // 區(qū)分函數(shù),block和OC方法的符號,函數(shù)與block是一樣的
        NSString *symbolName = ([name hasPrefix:@"+["] || [name hasPrefix:@"-["])? name: [@"_" stringByAppendingString:name];
        [sybleNames addObject:symbolName];
    }
    //反向遍歷數(shù)組
    NSEnumerator *enumerator = [sybleNames reverseObjectEnumerator];
    NSMutableArray *funArray = [NSMutableArray arrayWithCapacity:sizeof(sybleNames.count)];
    // 遍歷去除重復(fù)的符號
    NSString *name;
    while (name = [enumerator nextObject]) {
        if (![funArray containsObject:name]) {
            [funArray addObject:name];
        }
    }
    NSLog(@"%@",funArray);
}

6 生成order文件

最后一步,就把這些攔截到的符號寫入到文件中,代碼如下

    // 定義數(shù)組
    NSMutableArray<NSString *> *sybleNames = [NSMutableArray array];
    
    while (YES) {
        SymboNode *node =OSAtomicDequeue(&symbolList, offsetof(SymboNode, next));
        if (node == NULL) {
            break;
        }
        //獲取符號信息
        Dl_info info;
        dladdr(node->pc, &info);
        // 轉(zhuǎn)字符串
        NSString *name = @(info.dli_sname);
        // 區(qū)分函數(shù),block和OC方法的符號,函數(shù)與block是一樣的
        NSString *symbolName = ([name hasPrefix:@"+["] || [name hasPrefix:@"-["])? name: [@"_" stringByAppendingString:name];
        [sybleNames addObject:symbolName];
    }
    //反向遍歷數(shù)組
    NSEnumerator *enumerator = [sybleNames reverseObjectEnumerator];
    NSMutableArray *funArray = [NSMutableArray arrayWithCapacity:sizeof(sybleNames.count)];
    // 遍歷去除重復(fù)的符號
    NSString *name;
    while (name = [enumerator nextObject]) {
        if (![funArray containsObject:name]) {
            [funArray addObject:name];
        }
    }
    NSLog(@"%@",funArray);
    //去掉自己
    [funArray removeObject:[NSString stringWithFormat:@"%s", __func__]];
    // 寫入order文件
    // 變成字符串
    NSString *funcStr = [funArray componentsJoinedByString:@"\n"];
    
    // 存儲路徑
    NSString *filePath = [NSTemporaryDirectory() stringByAppendingString:@"/clangTrace.order"];
    // 文件
    NSData *file = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
    
    // 創(chuàng)建文件
    [[NSFileManager defaultManager] createFileAtPath:filePath contents:file attributes:nil];
    
    NSLog(@"%@", funcStr);

運(yùn)行之后, 我們把真機(jī)上的文件download下來,找到這個clangTrace.order文件,如圖


13

這樣就保存下來了,我們可以使用下order文件,如圖


14

運(yùn)行,我們看下map文件的內(nèi)容,如圖
15

跟我們order文件的順序一模一樣的。

7 Swift符號覆蓋

我們創(chuàng)建一個swift文件,如下

import UIKit

class SwiftTest: NSObject {
    
    @objc class public func swifttest () {
        print("swifttest......")
    }
}

然后在ViewController.m文件中的load方法加入

+ (void)load {
    
    [SwiftTest swifttest];
}

運(yùn)行下,如圖

16

這是沒有攔截到swift的方法,這個時候需要怎么解決呢?
我們需要加一下配置,如圖
17

sanitize-coverage=func和-sanitize=undefined參數(shù),運(yùn)行,如圖
18

這時候可以看到swift的方法也攔截住了,這里的swift符號是經(jīng)過混淆的,這是編譯器自動添加的。

總結(jié)

這篇文章我們通過Clang的插樁實(shí)現(xiàn)了二進(jìn)制重排,并在此過程解決了很多坑,本人在這個過程中也學(xué)習(xí)到了很多知識,文章有很多不足之處,不過還是希望可以給大家?guī)碇R。

附完整的代碼

// OC插樁標(biāo)記
-fsanitize-coverage=func,trace-pc-guard
// swift插樁標(biāo)記
sanitize-coverage=func
-sanitize=undefined

ClangTrace.h代碼如下

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface ClangTrace : NSObject

/// 生成order文件
/// @param filePath 文件路徑
void generateOrderFile(NSString *filePath);
@end

NS_ASSUME_NONNULL_END

ClangTrace.m代碼如下

#import "ClangTrace.h"
#include <stdint.h>
#include <stdio.h>
#include <sanitizer/coverage_interface.h>
#import <dlfcn.h>
#import <libkern/OSAtomic.h>

// 定義原子隊列
static OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;

// 定義符號的結(jié)構(gòu)
typedef struct {
    void * pc; // 函數(shù)地址
    void * next; // 下一個函數(shù)節(jié)點(diǎn)
}SymboNode;


@implementation ClangTrace

/// 生成order文件
/// @param filePath 文件路徑
void generateOrderFile(NSString *filePath) {
    // 定義數(shù)組
    NSMutableArray<NSString *> *sybleNames = [NSMutableArray array];
    while (YES) {
        SymboNode *node =OSAtomicDequeue(&symbolList, offsetof(SymboNode, next));
        if (node == NULL) {
            break;
        }
        //獲取符號信息
        Dl_info info;
        dladdr(node->pc, &info);
        // 轉(zhuǎn)字符串
        NSString *name = @(info.dli_sname);
        // 區(qū)分函數(shù),block和OC方法的符號,函數(shù)與block是一樣的
        NSString *symbolName = ([name hasPrefix:@"+["] || [name hasPrefix:@"-["])? name: [@"_" stringByAppendingString:name];
        [sybleNames addObject:symbolName];
    }
    //反向遍歷數(shù)組
    NSEnumerator *enumerator = [sybleNames reverseObjectEnumerator];
    NSMutableArray *funArray = [NSMutableArray arrayWithCapacity:sizeof(sybleNames.count)];
    // 遍歷去除重復(fù)的符號
    NSString *name;
    while (name = [enumerator nextObject]) {
        if (![funArray containsObject:name]) {
            [funArray addObject:name];
        }
    }
    //去掉自己
    [funArray removeObject:[@"_" stringByAppendingFormat:@"%s", __func__]];
    // 寫入order文件
    // 變成字符串
    NSString *funcStr = [funArray componentsJoinedByString:@"\n"];
    
    if ([ClangTrace isBlankString:filePath]) {
        // 存儲路徑
        filePath = [NSTemporaryDirectory() stringByAppendingString:@"/clangTrace.order"];
    }
    // 文件
    NSData *file = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
    
    // 創(chuàng)建文件
    [[NSFileManager defaultManager] createFileAtPath:filePath contents:file attributes:nil];
    NSLog(@"\n%@", funcStr);
}


/// 項目中的符號個數(shù)
/// @param start 起始位置
/// @param stop 結(jié)束位置
void __sanitizer_cov_trace_pc_guard_init(uint32_t *start, uint32_t *stop) {
//    static uint64_t N;  // Counter for the guards.
//    if (start == stop || *start) return;  // Initialize only once.
//    printf("INIT: %p %p\n", start, stop);
//    for (uint32_t *x = start; x < stop; x++)
//       *x = ++N;  // Guards should start from 1.
}


/// HOOK一切的回調(diào)函數(shù)
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
    void *PC = __builtin_return_address(0);
    // 創(chuàng)建結(jié)構(gòu)體
    SymboNode *node = malloc(sizeof(SymboNode));
    // 先給node賦值,下個節(jié)點(diǎn)暫時先為空
    *node = (SymboNode){PC, NULL};
    // 結(jié)構(gòu)體入棧,node存入symbolList,并把下一個地址給到node的next屬性
    OSAtomicEnqueue(&symbolList, node, offsetof(SymboNode, next));
}


/// 判斷字符串是否為空,返回YES字符串為空,NO相反
/// @param str 字符串
+ (BOOL)isBlankString:(NSString *)str {
    NSString *string = str;
    if (string == nil || string == NULL) {
        return YES;
    }
    if ([string isKindOfClass:[NSNull class]]) {
        return YES;
    }
    if ([[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] length]==0) {
        return YES;
    }
    return NO;
}

@end
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容