【iOS】锁

线程安全

当一个线程访问数据的时候,其他的线程不能对其进行访问,直到该线程访问完毕。简单来讲就是在同一时刻,对同一个数据操作的线程只有一个。而线程不安全,则是在同一时刻可以有多个线程对该数据进行访问,从而得不到预期的结果。 在iOS中, UIKit是绝对线程安全的,因为UIKit都是在主线程操作的,单线程没有线程当然没有线程安全问题,但除此之外,其他都要考虑线程安全问题

iOS解决线程安全的途径其原理大同小异,都是通过锁来使关键代码保证同步执行,从而确保线程安全性,这一点和多线程的异步执行任务是不冲突的。

注: 不要将过多的其他操作代码放到锁里面,否则一个线程执行的时候另一个线程就一直在等待,就无法发挥多线程的作用了

下方我们就详细讲解iOS相关锁,本博客采用一个经典的售票例子:

此处展示的是不加锁(即不考虑线程安全)的情况:

objectivec 复制代码
// .h 中
#import <Cocoa/Cocoa.h>

@interface ViewController : NSViewController
@property (nonatomic, assign) NSInteger ticketSurplusCount;

@end


// .m中
// 记录共出售多少票的全局变量
int cnt = 0;

- (void)startSell {
    // 一共有50张票
    self.ticketSurplusCount = 50;
    
    __weak typeof (self) weakSelf = self;
    
    // 一号售卖口
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [weakSelf saleTick];
    });
    
    // 二号售卖口
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [weakSelf saleTick];
    });
}

- (void)saleTick {
    while(1) {
        if (self.ticketSurplusCount > 0) {
            self.ticketSurplusCount--;
            cnt++;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else {
            NSLog(@"所有火车票均已售完,共售出%d张票", cnt);
            break;
        }
    }
}

运行结果:

我们看到运行结果显示整个卖票的过程是错乱的,居然一共卖出了51张票,接下来我们就在讲解锁的过程中对卖票操作加锁,来修正现在的错乱结果。

锁的种类

iOS中的锁有两大类:自旋锁、互斥锁

自旋锁

与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环尝试,直到该自旋锁的保持者已经释放了锁(忙等待)。因为不会引起调用者睡眠,所以效率高于互斥锁。

自旋锁原理

线程一直是running(加锁------>解锁),死循环检测锁的标志位,机制不复杂。

自旋锁缺点

  • 调用者在未获得锁的情况下,一直运行--自旋,所以占用着CPU资源,如果不能在很短的时间内获得锁,会使CPU效率降低。所以自旋锁就主要用在临界区持锁时间非常短且CPU资源不紧张的情况下。
  • 在用自旋锁时有可能造成死锁,当递归调用时有可能造成死锁。

OSSpinLock(自旋锁)

OSSpinLock是在libkern库中,使用之前需要引入头文件<libkern/OSAtomic.h>,使用时会出现警告⚠️。

这是因为OSSpinLock存在缺陷,从iOS10开始已经不建议使用了。官方建议使用os_unfair_lock来替代。

objectivec 复制代码
// 初始化
spinLock = OS_SPINKLOCK_INIT;
// 加锁
OSSpinLockLock(&spinLock);
// 解锁
OSSpinLockUnlock(&spinLock);

实际使用(在卖票例子中):

objectivec 复制代码
- (void)saleTick {
    
    while(1) {
        // 加锁
        OSSpinLockLock(&_spinLock);
        if (self.ticketSurplusCount > 0) {
            self.ticketSurplusCount--;
            cnt++;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else {
            NSLog(@"所有火车票均已售完,共售出%d张票", cnt);
            
            // 解锁
            OSSpinLockUnlock(&_spinLock);
            break;
        }
        // 解锁
        OSSpinLockUnlock(&_spinLock);
    }
}

运行结果:

结果就是按照顺序非常规范地卖出了这50张票

刚才提到了OSSpinLock存在缺陷,其实它的缺陷主要存在两点:

  • OSSpinLock不会记录持有它的线程信息,当发生优先级反转的时候,系统找不到低优先级的线程,导致系统可能无法通过提高优先级解决优先级反转问题
  • 高优先级线程使用自旋锁忙等待的时候一直在占用CPU时间片,导致低优先级线程拿到时间片的概率降低。

值得注意的是: 自旋锁和优先级反转没有关系,但是正因为有上面两点,所以自旋锁会导致优先级反转问题更难解决,甚至造成更为严重的线程等待问题,所以苹果就废除了OSSpinLock,转而推荐人们使用os_unfair_lock来替代,由于os_unfair_lock是一个互斥锁,所以我们将对其的讲解放到互斥锁中去。

互斥锁

保证在任何时候,都只有一个线程访问对象。当获取锁操作失败时,线程会进入睡眠,等待锁释放时被唤醒。

互斥锁原理

线程会从sleep(加锁)------> running(解锁),过程中有上下文的切换,cpu的抢占,信号的发送等开销,所以效率是要低于自旋锁的。

互斥锁分为两种: 递归锁、非递归锁

  • 递归锁:可重入锁,同一个线程在锁释放前可再次获取锁,即可以递归调用。
  • 非递归锁:不可重入,必须等锁释放后才能再次获取锁。

os_unfair_lock

上面讲过现在苹果采用os_unfair_lock来代替不安全的OSSpinLock,且由于os_unfair_lock会休眠而不是忙等,所以属于 互斥锁 ,且是非递归互斥锁,下面来看一下它的用法:

os_unfair_lockos库中,使用之前需要导入头文件<os/lock.h>

objectivec 复制代码
//创建一个锁
os_unfair_lock unfairLock;
//初始化
unfairLock = OS_UNFAIR_LOCK_INIT;
//加锁
os_unfair_lock_lock(&unfairLock);
//解锁
os_unfair_lock_unlock(&unfairLock);

实际使用(在卖票例子中):

objectivec 复制代码
- (void)saleTickWithOsUnfairLock {
    while(1) {
        // 加锁
        os_unfair_lock_lock(&unfairLock);
        if (self.ticketSurplusCount > 0) {
            self.ticketSurplusCount--;
            cnt++;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else {
            NSLog(@"所有火车票均已售完,共售出%d张票", cnt);
            
            // 解锁
            os_unfair_lock_unlock(&unfairLock);
            break;
        }
        // 解锁
        os_unfair_lock_unlock(&unfairLock);
    }
}

运行结果:

关于它的定义:

可以看到这里的解释是,不是旋转(忙等),而是休眠,等待被唤醒,所以os_unfair_lock理应是互斥锁。

pthread_mutex

pthread_mutex就是 互斥锁 本身------当锁被占用,而其他线程申请锁时,不是使用忙等,而是阻塞线程并睡眠,另外pthread_mutex也是非递归的锁。

使用时我们需要先引用这个头文件:#import <pthread.h>

具体使用如下:

objectivec 复制代码
// 全局声明互斥锁
pthread_mutex_t _lock;
// 初始化互斥锁
pthread_mutex_init(&_lock, NULL);
// 加锁
pthread_mutex_lock(&_lock);
// 这里做需要线程安全操作
// ...
// 解锁 
pthread_mutex_unlock(&_lock);
// 释放锁
pthread_mutex_destroy(&_lock);

运行结果如下:

结果就是按照顺序非常规范地卖出了这50张票。

NSLock

我们的Foundation框架内部也是有一把NSLock锁的,使用起来非常方便,基于互斥锁pthroad_mutex封装而来,是一把互斥非递归锁。

使用如下:

objectivec 复制代码
//初始化NSLock
NSLock *lock = [[NSLock alloc] init];
//加锁
[lock lock];
...
//线程安全执行的代码
...
//解锁
[lock unlock];

实际使用(在卖票例子中):

objectivec 复制代码
- (void)saleTickWithNSLock {
    while(1) {
        // 加锁
        [lock lock];
        if (self.ticketSurplusCount > 0) {  // 如果还有票,继续售卖
            self.ticketSurplusCount--;
            cnt++;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else { // 如果已卖完,关闭售票窗口
            NSLog(@"所有火车票均已售完,共售出%d张票", cnt);
            // 解锁
            [lock unlock];
            break;
        }
        // 解锁
        [lock unlock];
    }
}

运行结果如下:

结果就是按照顺序非常规范地卖出了这50张票。

如果对非递归锁强行使用递归调用,就会在调用时发生线程阻塞,而并非是死锁,第一次加锁之后还没出锁就进行递归调用,第二次加锁就堵塞了线程。

苹果官方文档的描述如下:

可以看到在同一线程上调用两次NSLocklock方法将会永久锁定线程。同时也重点提醒向NSLock对象发生解锁消息时,必须确保消息时从发送初始锁定消息的同一个线程发送的,否则就会产生未知问题。

非递归互斥锁导致线程阻塞的例子:

objectivec 复制代码
- (void)saleTickWithNSLock {
    while(1) {
        // 加锁
        [lock lock];
        if (self.ticketSurplusCount > 0) {  // 如果还有票,继续售卖
            self.ticketSurplusCount--;
            cnt++;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else { // 如果已卖完,关闭售票窗口
            NSLog(@"所有火车票均已售完,共售出%d张票", cnt);
            // 解锁
            break;
        }
        // 解锁
    }
}

运行结果如下:

可以看到,因为我们对当前这个线程在执行lock操作后还未unlock的情况下,又进行了NSLock的重复lock加锁操作,所以当前线程发生了阻塞,只进行了一次卖票操作就再不执行其他操作了。

NSRecusiveLock

NSRecursiveLock使用和NSLock类似,不过NSRecursiveLock是递归互斥锁。

objectivec 复制代码
//初始化NSLock
NSRecusiveLock *recusiveLock = [[NSRecusiveLock alloc] init];
//加锁
[recusiveLock lock];
...
//线程安全执行的代码
...
//解锁
[recusiveLock unlock];

下面我们举一个NSRecursiveLock递归使用的例子:

objectivec 复制代码
@interface ViewController ()
@property (nonatomic, assign) NSInteger ticketSurplusCount;
@property (nonatomic, strong) NSRecursiveLock *recursiveLock;

@end


//卖票窗口(此处我们循环创建十个窗口,但是都是在同一线程中执行)
- (void)threadBlock {   
	//一共50张票
	self.ticketSurplusCount = 50;
	//初始化NSRecursiveLock递归锁
    _recursiveLock = [[NSRecursiveLock alloc] init];
	
    __weak typeof (self) weakSelf = self;

    for (int i = 0; i < 10; ++i) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [weakSelf saleTicket];
        });
    }
}

//卖票的函数
- (void)saleTicket {
    //加锁
    [_recursiveLock lock];
    if (self.ticketSurplusCount > 0) {  // 如果还有票,继续售卖
        self.ticketSurplusCount--;
        cnt++;
        NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
        [NSThread sleepForTimeInterval:0.2];
        //递归调用卖票函数
        [self saleTicket];
    } else { // 如果已卖完,关闭售票窗口
        NSLog(@"所有火车票均已售完,共售出%d张票", cnt);
    }
    //解锁
    [_recursiveLock unlock];
}

运行结果如下:

可以看到向同一个线程多次获取递归锁NSRecusiveLock并不会导致程序死锁,而是正常的线程安全地加锁执行。

苹果官方文档的描述如下:

同一线程可以多次获取而不会导致死锁的锁,重点是在同一线程。

举一个不同线程获取锁导致死锁的例子:

objectivec 复制代码
- (void) recursiveDeadlocksWithValue:(int)value {
    [recursiveLock lock];
    NSLog(@"%d---%@", value, [NSThread currentThread]);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_enter(group);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        if (value > 0) {
            [self recursiveDeadlocksWithValue:value - 1];
        }
        dispatch_group_leave(group);
    });
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    [recursiveLock unlock];
}

可以看到里面的线程想要获取锁就必须等待外面的线程释放锁,而外面的线程释放锁需要等待里面的线程完成任务,导致互相等待死锁。

NSCondition

NSCondition是一个条件锁,同时其实也是一个非递归互斥锁,可能平时用的不多,但与GCD信号量相似:线程1需要等到条件1满足才会往下走,否则就会堵塞等待,直至条件满足,一旦获得了锁并执行了代码的关键部分,线程就可以放弃该锁并将关联条件设置为新的条件。条件本身是任意的:可以根据应用程序的需要定义它们。

Objective-C代码并不能看到NSCondition的具体实现,只能看到该类的接口部分,实现部分需要使用swift源码进行查看:

objectivec 复制代码
OC接口部分:
@interface NSCondition : NSObject <NSLocking> {
@private
    void *_priv;
}

- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end
swift 复制代码
swift实现部分:
open class NSCondition: NSObject, NSLocking {
    internal var mutex = _MutexPointer.allocate(capacity: 1)
    internal var cond = _ConditionVariablePointer.allocate(capacity: 1)

    public override init() {
        pthread_mutex_init(mutex, nil)
        pthread_cond_init(cond, nil)
    }
    
    deinit {
        pthread_mutex_destroy(mutex)
        pthread_cond_destroy(cond)
        mutex.deinitialize(count: 1)
        cond.deinitialize(count: 1)
        mutex.deallocate()
        cond.deallocate()
    }
    
    // 一般用于多线程同时访问、修改同一个数据源,保证在同一 时间内数据源只被访问、修改一次,
    // 其他线程的命令需要在lock 外等待,只到 unlock ,才可访问
    open func lock() {
        pthread_mutex_lock(mutex)
    }
    
    // 释放锁,与lock成对出现
    open func unlock() {
        pthread_mutex_unlock(mutex)
    }
    
    // 让当前线程处于等待状态,阻塞
    open func wait() {
        pthread_cond_wait(cond, mutex)
    }

    // 让当前线程等待到某个时间,阻塞
    open func wait(until limit: Date) -> Bool {
        guard var timeout = timeSpecFrom(date: limit) else {
            return false
        }
        return pthread_cond_timedwait(cond, mutex, &timeout) == 0
    }
    
    // 发信号告诉线程可以继续执行,唤醒线程
    open func signal() {
        pthread_cond_signal(cond)
    }
    
    //唤醒所有正在等待的线程
    open func broadcast() {
        pthread_cond_broadcast(cond) // wait  signal
    }
    
    open var name: String?
}

可以看到,该对象还是对pthread_mutex的一层封装,NSCondition也是一种互斥锁。当我们需要等待某个条件的时候,也就是条件不满足的时候,就可以使用wait方法来阻塞线程,当条件满足了,使用signal方法发送信号唤醒线程。

再浅浅总结一下:

  • NSCondition是对mutexcond的一种封装(cond就是用于访问和操作特定类型数据的指针)。
  • wait操作会阻塞线程,使其进入休眠状态,直至超时。
  • signal操作是唤醒一个正在休眠等待的线程。
  • broadcast会唤醒所有正在等待的线程。

实际使用(在卖票例子中):

objectivec 复制代码
//售票的方法
- (void)saleTicketSafeWithConditionLock {
    while (1) {
        // 加锁
        [_condition lock];
        if (self.ticketSurplusCount > 0) {  // 如果还有票,继续售卖
            self.ticketSurplusCount--;
            cnt++;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else { // 如果已卖完,关闭售票窗口
            NSLog(@"所有火车票均已售完,共售出%d张票", cnt);
            // 解锁
            [_condition unlock];
            break;
        }
        // 解锁
        [_condition unlock];
    }
}

运行结果:

结果就是按照顺序非常规范地卖出了这50张票。

NSConditionLock

NSConditionLockNSCondition又做了一层封装,自带条件探测,能够更简单灵活的使用,所以它也属于非递归互斥锁。

然后我们来看一看NSConditionLock的相关源码:

NSCondition的源码一样,在OC中看接口,在swift中看实现:

objectivec 复制代码
OC的接口部分:
@interface NSConditionLock : NSObject <NSLocking> {
@private
    void *_priv;
}

- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;

@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end
swift 复制代码
swift中实现:
internal var _cond = NSCondition()
internal var _value: Int
internal var _thread: _swift_CFThreadRef?
    
public convenience override init() {
    self.init(condition: 0)
}
    
public init(condition: Int) {
    _value = condition
}

// 表示 xxx 期待获得锁,
// 如果没有其他线程获得锁(不需要判断内部的 condition) 那它能执行此行以下代码,
// 如果已经有其他线程获得锁(可能是条件锁,或者无条件 锁),则等待,直至其他线程解锁
open func lock() {
    let _ = lock(before: Date.distantFuture)
}

open func unlock() {
    _cond.lock()
    _thread = nil
    _cond.broadcast()
    _cond.unlock()
}
    
open var condition: Int {
    return _value
}

// 表示如果没有其他线程获得该锁,但是该锁内部的 condition不等于A条件,它依然不能获得锁,仍然等待。
// 如果内部的condition等于A条件,并且没有其他线程获得该锁,则执行任务,同时设置它获得该锁
// 其他任何线程都将等待它代码的完成,直至它解锁。
open func lock(whenCondition condition: Int) {
    let _ = lock(whenCondition: condition, before: Date.distantFuture)
}

open func `try`() -> Bool {
    return lock(before: Date.distantPast)
}
    
open func tryLock(whenCondition condition: Int) -> Bool {
    return lock(whenCondition: condition, before: Date.distantPast)
}

// 表示释放锁,同时把内部的condition设置为A条件
open func unlock(withCondition condition: Int) {
    _cond.lock()
    _thread = nil
    _value = condition
    _cond.broadcast()
    _cond.unlock()
}

open func lock(before limit: Date) -> Bool {
    _cond.lock()
    while _thread != nil {
        if !_cond.wait(until: limit) {
            _cond.unlock()
            return false
        }
    }
    _thread = pthread_self()
    _cond.unlock()
    return true
}
    
// 表示如果被锁定(没获得 锁),并超过该时间则不再阻塞线程。
// 需要注意的是:返回的值是NO,它没有改变锁的状态,这个函数的目的在于可以实现两种状态下的处理
open func lock(whenCondition condition: Int, before limit: Date) -> Bool {
    _cond.lock()
    while _thread != nil || _value != condition {
        if !_cond.wait(until: limit) {
            _cond.unlock()
            return false
        }
    }
    _thread = pthread_self()
    _cond.unlock()
    return true
}
    
open var name: String?

可以看出,触发的唤醒线程的条件是传入的condition取值,和我们创建锁的时候值要相同,我们可以在释放当前线程锁的时候重新设置其他线程传入的condition值,这样也就达到了唤醒其他线程的目的。如果创建锁的值和传入的值都不能匹配,则会进入阻塞状态

也就是说NSConditionLockinit lockunlock中都可以传入value。

例:

objectivec 复制代码
- (void)conditionLockTest {
    for (int i = 0; i < 5; ++i) {
        //调用测试函数
        [self test];
        //修改Condition参数值为3
        [self.conditionLock lockWhenCondition:0];
        [self.conditionLock unlockWithCondition:3];
    }
    return;
}

//测试函数
- (void)test {
    self.conditionLock = [[NSConditionLock alloc] initWithCondition:3];
    dispatch_queue_t globalQ = dispatch_get_global_queue(0, 0);
    dispatch_async(globalQ, ^{
        [self.conditionLock lockWhenCondition:3];
        NSLog(@"任务1");
        [self.conditionLock unlockWithCondition:2];
    });
    
    dispatch_async(globalQ, ^{
        [self.conditionLock lockWhenCondition:2];
        NSLog(@"任务2");
        [self.conditionLock unlockWithCondition:1];
    });
    
    dispatch_async(globalQ, ^{
        [self.conditionLock lockWhenCondition:1];
        NSLog(@"任务3");
        [self.conditionLock unlockWithCondition:0];
    });
}

运行结果:

我们看到每次打印的结果都是严格按照任务1、任务2、任务3执行的。

总结NSConditionLockNSCondition

相同点:

  • 都是互斥锁。
  • 通过条件变量来控制加锁、释放锁,从而达到阻塞线程、唤醒线程的目的。

不同点:

  • NSCondition是基于对pthread_mutex的封装,而NSConditionLock是对NSCondition做了一层封装。
  • NSCondition是需要手动让线程进入等待状态阻塞线程、释放信号唤醒线程,NSConditionLock则只需要外部传入一个值,就会依据这个值进行自动判断是阻塞线程还是唤醒线程。

Semaphore信号量

Semaphore信号量也可以解决线程安全问题,GCD 中的信号量是指 Dispatch Semaphore,是持有计数的信号。类似于过高速路收费站的栏杆。可以通过时,打开栏杆,不可以通过时,关闭栏杆。在 Dispatch Semaphore 中,使用计数来完成这个功能,计数小于 0 时需要等待,不可通过。计数为 0 或大于 0 时,不用等待可通过。计数大于 0 且计数减 1 时不用等待,可通过。

Dispatch Semaphore 提供了三个方法:

objectivec 复制代码
dispatch_semaphore_create://创建一个 Semaphore 并初始化信号的总量
dispatch_semaphore_signal://发送一个信号,让信号总量加 1
dispatch_semaphore_wait://可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。

注意: 信号量的使用前提是:想清楚你需要处理哪个线程等待(阻塞),又要哪个线程继续执行,然后使用信号量

Dispatch Semaphore 在实际开发中主要用于:

  • 保持线程同步,将异步执行任务转换为同步执行任务。
  • 保证线程安全,为线程加锁。

@synchronized

@synchronized可能是日常开发中用的比较多的一种递归互斥锁,因为它的使用比较简单,但并不是在任意场景下都能使用@synchronized,且它的性能较低。

使用方法如下:

objectivec 复制代码
@synchronized (obj) {}

下面我们来探索一下@synchronized的源码:

  • 通过汇编能发现@synchronized就是实现了objc_sync_enterobjc_sync_exit两个方法。
  • 通过符号断点能知道这两个方法都是在objc源码中的。
  • 通过clang也能得到一些信息。
objectivec 复制代码
#pragma clang assume_nonnull end

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        { id _rethrow = 0; id _sync_obj = (id)__null; objc_sync_enter(_sync_obj);
try {
	struct _SYNC_EXIT { _SYNC_EXIT(id arg) : sync_exit(arg) {}
	~_SYNC_EXIT() {objc_sync_exit(sync_exit);}
	id sync_exit;
	} _sync_exit(_sync_obj);

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_6p_mn3hwpz14_7dg_gr79rtm4n80000gn_T_main_59328a_mi_0);
        } catch (id e) {_rethrow = e;}
{ struct _FIN { _FIN(id reth) : rethrow(reth) {}
	~_FIN() { if (rethrow) objc_exception_throw(rethrow); }
	id rethrow;
	} _fin_force_rethow(_rethrow);}
}

    }
    return 0;
}
相关推荐
2501_915918411 小时前
Wireshark、Fiddler、Charles抓包工具详细使用指南
android·ios·小程序·https·uni-app·iphone·webview
SoraLuna2 小时前
KuiklyUI for OpenHarmony 实战 01:源码构建与运行(Mac)
macos·ui·鸿蒙
Wcowin2 小时前
为macOS Finder提供直观的剪切粘贴体验
macos
JERRY. LIU3 小时前
Mac 笔记本通用快捷键大全
linux·macos
all79807969711 小时前
黑苹果macos 15 Sequoia升级 macos 26.1 Tahoe小结
macos
唯一浩哥12 小时前
2026 年,macbook air 2015 升级注意事项
macos·macbook·macbook air 2015·升级硬盘
TheNextByte114 小时前
如何将文件从Android无线传输到 iPad
android·ios·ipad
2501_9151063216 小时前
如何在iPad上高效管理本地文件的完整指南
android·ios·小程序·uni-app·iphone·webview·ipad
2501_9151063217 小时前
iOS 成品包加固,在只有 IPA 的情况下,能做那些操作
android·ios·小程序·https·uni-app·iphone·webview
Free Tester17 小时前
在iPhone上显示点击屏幕点击的方法
功能测试·ios·iphone