【iOS】锁

news2024/11/19 21:26:41

线程安全

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

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

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

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

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

// .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来替代。

// 初始化
spinLock = OS_SPINKLOCK_INIT;
// 加锁
OSSpinLockLock(&spinLock);
// 解锁
OSSpinLockUnlock(&spinLock);

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

- (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>

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

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

- (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>
具体使用如下:

// 全局声明互斥锁
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封装而来,是一把互斥非递归锁。
使用如下:

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

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

- (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对象发生解锁消息时,必须确保消息时从发送初始锁定消息的同一个线程发送的,否则就会产生未知问题。

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

- (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是递归互斥锁。

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

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

@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并不会导致程序死锁,而是正常的线程安全地加锁执行。

苹果官方文档的描述如下:
在这里插入图片描述

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

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

- (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源码进行查看:

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实现部分:
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会唤醒所有正在等待的线程。

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

//售票的方法
- (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中看实现:

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中实现:
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。
例:

- (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 提供了三个方法:

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

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

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

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

@synchronized

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

使用方法如下:

@synchronized (obj) {}

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

  • 通过汇编能发现@synchronized就是实现了objc_sync_enterobjc_sync_exit两个方法。
  • 通过符号断点能知道这两个方法都是在objc源码中的。
  • 通过clang也能得到一些信息。
#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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/830666.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode--剑指Offer75(1)

目录 题目描述&#xff1a;剑指 Offer 05. 替换空格&#xff08;简单&#xff09;题目接口解题思路1代码解题思路2代码 PS: 题目描述&#xff1a;剑指 Offer 05. 替换空格&#xff08;简单&#xff09; 请实现一个函数&#xff0c;把字符串 s 中的每个空格替换成"%20&quo…

Qt Creator中designer使用QWebEngine异常排查

Qt Creator中designer使用QWebEngine异常排查 1、前提背景 最近由于版权的原因&#xff0c;我们采取了自编译的Qt Creator。编译完成之后启动Qt Creator刚开始一切都是很顺利。 但是在Creator中打开designer&#xff0c;使用QWebEngine控件就发生了异常&#xff0c;Qt Creat…

新一代图像合成模型:Stable Diffusion XL(SDXL)上线!

几个使用Stable Diffusion XL 1.0生成的图像示例。 新的SDXL 1.0发布允许在本地计算机上运行的高分辨率人工智能图像合成。 周三&#xff0c;Stability AI发布了其下一代开源权重人工智能图像合成模型Stable Diffusion XL 1.0&#xff08;SDXL&#xff09;。它可以根据文本描述…

有多卷?智慧金融可视化大屏可以这样子

科学技术不断发展&#xff0c;数字化转型不断加快&#xff0c;智慧金融正成为金融业的新引擎。数字孪生、大数据、物联网等新一代信息技术在推动智慧金融更加强调效率、优化精准营销。数据可视化大屏如何为金融单位提供低代码、定制化的服务&#xff0c;让金融单位的数据可视、…

Python编程从入门到实践练习第三章:列表简介

目录 一、字符串1.1 在字符串中使用变量 二、列表2.1 遍历列表练习题代码 2.2 列表元素的插入和删除涉及方法练习题代码 2.3 组织列表涉及方法练习题代码 2.4 索引 参考书&#xff1a;Python从入门到实践&#xff08;第二版&#xff09; 一、字符串 1.1 在字符串中使用变量 f…

【力扣】92. 反转链表 II <链表指针>

【力扣】92. 反转链表 II 给你单链表的头指针 head 和两个整数 left 和 right &#xff0c;其中 left < right。请你反转从位置 left 到位置 right 的链表节点&#xff0c;返回反转后的链表。 示例 1 输入&#xff1a;head [1,2,3,4,5], left 2, right 4 输出&#xff…

JVM面试突击1

JVM面试突击 JDK&#xff0c;JRE以及JVM的关系 我们的编译器到底干了什么事&#xff1f; 仅仅是将我们的 .java 文件转换成了 .class 文件&#xff0c;实际上就是文件格式的转换&#xff0c;对等信息转换。 类加载机制是什么&#xff1f; 所谓类加载机制就是 虚拟机把Class文…

Prometheus实现系统监控报警邮件

Prometheus实现系统监控报警邮件 简介 Prometheus将数据采集和报警分成了两个模块。报警规则配置在Prometheus Servers上&#xff0c; 然后发送报警信息到AlertManger&#xff0c;然后我们的AlertManager就来管理这些报警信息&#xff0c;聚合报警信息过后通过email、PagerDu…

怎么迅速做出高端、还会动的数据图表?来看看这五个大数据可视化神器!

什么叫大数据可视化&#xff1f; 其实很简单。大数据可视化就是指通过图表、图形、地图等视觉化方式&#xff0c;将庞大、复杂的大数据集合转化为直观、易于理解和分析的图像展示。 它的目的是帮助人们更好地理解和解释大数据&#xff0c;发现数据中的模式、趋势和关联&#…

CLion中avcodec_receive_frame()问题

1. 介绍 在提取音视频文件中音频的PCM数据时&#xff0c;使用avcodec_receive_frame()函数进行解码时&#xff0c;遇到了一些问题&#xff0c;代码在Visual Studio 2022中运行结果符合预期&#xff0c;但是在CLion中运行时&#xff0c;获取的AVFrame有错误&#xff0c;和VS中获…

谈「效」风生 |“效能指标”,该由谁来定义?

#第5期&#xff1a;效能指标&#xff0c;该由谁来定义&#xff1f;# 回顾上期《「自动化」聊起来简单&#xff0c;做起来难》我们聊了聊如何打造「自动化」的事&#xff0c;这也是真正实现研发效能提升的必要条件。从单点自动化提升效率&#xff0c;到全工具链自动化&#xff…

【Java环境不会搭建?一文带你读懂Windows下安装Java!】

JKD下载网址 —— https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html 1、如果你是32位系统下载 jdk-8u241-windows-i586.exe&#xff08;32位&#xff09;&#xff1b; 2、如果你是64位系统下载 jdk-8u241-windows-x64.exe&#xff08;64位&…

【freespace】HybridNets: End-to-End Perception Network

目录 摘要 1. 介绍 1.1. 背景 1.2. 相关工作 2. 方法 2.1. 网络体系结构 2.2. 编码器 2.3. 译码器 2.4. 损失函数和训练 3. 实验与评估 3.1. 实验设置 3.2. 评价指标 3.3. 成本计算性能 3.4. 多任务性能 4. 结论与展望 摘要 端到端网络在多任务处理中变得越来越重要…

Godot 4 源码分析 - 增加格式化字符串功能

Godot 4的主要字符串类型为String&#xff0c;已经设计得比较完善了&#xff0c;但有一个问题&#xff0c;格式化这块没怎么考虑。 String中有一个format函数&#xff0c;但这个函数只有两个参数&#xff0c;这咋用&#xff1f; String String::format(const Variant &va…

Rocketmq 定时消息源码分析

定时消息定义 生产者将消息投放到broker后&#xff0c;不会马上被消费者消费。需要等待到特定时间才会被消费。 调用链路 producer 将定时消息写入commitLog线程ReputThead 休息1毫秒&#xff0c;读取一次commitlog数据&#xff0c;写入ConsumeQueue和IndexFile线程Scheduled…

所学即所用:方飞将AI技术运用于反偷猎领域

原创 | 文 BFT机器人 方飞&#xff0c;高中毕业于江苏省常州高级中学&#xff0c;于2007年进入清华大学电子工程系攻读学士学位&#xff0c;2011年本科毕业后赴美国南加州大学计算机系攻读博士&#xff0c;主要从事安全博弈研究&#xff0c;师从安全博弈领域的权威专家 Milind…

vxworks文件系统分析

参考https://www.freebuf.com/articles/endpoint/335030.html 测试固件 https://service.tp-link.com.cn/detail_download_7989.html 固件提取 binwalk解压固件&#xff0c;在第一部分即为要分析的二进制文件&#xff0c;可以拖进ida分析 设置为arm小端字节序&#xff0c;点…

爆火的“为i做e”梗,小红书如何成为年轻人的社交货币?

话题浏览超13亿&#xff0c;“新社交密码”抢占用户心智 2023-08-03 草稿临时预览&#xff0c;有效期剩余59分59秒 请勿包含诱导分享&#xff0c;虚假中奖&#xff0c;违法违纪等信息。 爆火的“为i做e”梗、将MBTI写进个人简介、花样百出的MBI梗图 ...... 从去年5月到现在&…

手把手教你安装Eclipse最新版本的详细教程 (非常详细,非常实用)

简介 首先声明此篇文章主要是针对测试菜鸟或者刚刚入门的小伙们或者童鞋们&#xff0c;大佬就没有必要往下看了。 写这篇文章的由来是因为后边要用这个工具&#xff0c;但是由于某些原因有部分小伙伴和童鞋们可能不会安装此工具&#xff0c;为了方便小伙伴们和童鞋们的后续学习…

第五届宁波市卫生健康系统信息化技能竞赛暨赛前培训成功举办 平凯星辰受邀授课

近日&#xff0c; 第五届宁波市卫生健康系统第五届信息化技能竞赛暨赛前培训在宁波饭店成功举办 。本次培训吸引了来自区、县、市属各级医疗单位的信息化相关负责人参与。宁波市卫生信息中心副主任唐玲作主题发言&#xff0c; 平凯星辰作为中国数据库代表厂商&#xff0c;受邀进…