【加密解密】加密解密介绍

Base64编码

据我说知,苹果并没有提供API来是实现Base64编码,所以需要看官在网上寻找验证,还好,这并不难

感谢Lonely__angelababa的升迁,苹果是有Base64的API,截图如下:

苹果提供Base64API.png

Base64编码的思索是是采取64个基本的ASCII码字符对数据进行重新编码。它将索要编码的多少拆分成字节数组。以3个字节为一组。按顺序排列24 位数据,再把这24位数据分为4组,即每组6位。再在每组的的最高位前补两个0凑足一个字节。这样就把一个3字节为一组的数据重新编码成了4个字节。当所要编码的数据的字节数不是3的整倍数,也就是说在分组时最终一组不够3个字节。这时在最后一组填充1到2个0字节。并在终极编码完成后在终极添加1到2个
“=”。

例:将对ABC进行BASE64编码:

1、首先取ABC对应的ASCII码值。A(65)B(66)C(67);

2、再取二进制值A(01000001)B(01000010)C(01000011);

3、然后把这多少个字节的二进制码接起来(010000010100001001000011);

4、
再以6位为单位分成4个数据块,并在最高位填充多少个0后形成4个字节的编码后的值,(00010000)(00010100)(00001001)(00000011),其中加色部分为实在数据;

5、再把那三个字节数据转化成10进制数得(16)(20)(9)(3);

6、最终依据BASE64给出的64个要旨字符表,查出对应的ASCII码字符(Q)(U)(J)(D),这里的值实际就是数据在字符表中的索引。

Base64编码表

解码过程就是把4个字节再还原成3个字节再依照不同的数据格局把字节数组重新整理成多少。

Base64很直观的目的就是让二进制文件转发为64个主导的ASCII码字符。

7. 想要开辟新线程必须让任务在异步执行,想要开辟六个线程,唯有让任务在相互队列中异步执行才可以。执行形式和队列类型多层组合在一定水平上可以实现对于代码执行顺序的调度。

上边我们转移一下任务的预先级:
invocationOper.queuePriority = NSOperationQueuePriorityVeryLow;

运转结果:

ThreadDemo[4894:1218440] QUEUEBlockOperationRun_<NSThread: 0x608000268880>{number = 3, name = (null)}
ThreadDemo[4894:1218442] NSBlockOperationRun_<NSThread: 0x60000026d340>{number = 4, name = (null)}
ThreadDemo[4894:1218457] NSInvocationOperationRun_<NSThread: 0x60000026d400>{number = 5, name = (null)}

咱俩发现优先级低的任务会后推行,可是,那并不是纯属的,还有不少事物可以左右CPU分配,以及操作系统对于任务和线程的操纵,只可以说,优先级会在必然水平上让优先级高的天职起首实践。同时,优先级只对同一队列中的任务使得哦。下面我们就看一个会忽视优先级的情况。

AES

系统也并不曾一向提供诸如DES、AES的API,但是提供了加密解密的连锁操作CommonCrypto,DES或者AES的落实,需要我们团结包裹一下。

加密是由算法/模式/填充重组的,算法是DES,AES等,
格局是EBC,CBC等,iOS和Android的填充是不同等的:

mac支持:

NoPadding (NoPadding就是不填充,相当于自定义填充)

PKCS7Padding

而java支持:

NoPadding

ISO10126Padding

OAEPPadding, OAEPWith<digest>And<mgf>Padding

PKCS1Padding

PKCS5Padding

SSL3Padding

接下去我们引入一些背景知识:

在密码学中,分组加密(Block
cipher,又称分块加密),是一种对称密钥算法。它将公开分成两个等长的模块(block),使用规定的算法和对称密钥对每组分别加密解密。分组加密是极其首要的加密协议组成,其中卓越的如DES和AES作为美利坚联邦合众国政坛仲裁的标准加密算法,应用领域从电子邮件加密到银行交易转帐,至极广阔。

密码学中的工作模式:

最早出现的做事情势,ECB,CBC,OFB和CFB可以追溯到1981年。2001年,NIST修订了其原先发布的干活形式工作列表,参加了AES,并参与了CTR形式。最终,在二零一零年三月,NIST参与了XTS-AES,而其余的可信格局并没有为NIST所认证。例如CTS是一种密文窃取的格局,许多广大的密码学运行库提供了这种情势。

密码学中,块密码的工作情势允许利用同一个块密码密钥对多于一块的多少举行加密,并确保其安全性。块密码自身只好加密长度等于密码块长度的单块数据,若要加密变长数据,则数据必须先被分开为部分单独的密码块。平时而言,最后一块数据也亟需利用方便填充方式将数据扩充到适合密码块大小的长度。一种工作情势描述了加密每一数据块的长河,并时时使用基于一个通常称为初始化向量的附加输入值以开展随机化,以管教安全。

初始化向量

起首化向量(IV,Initialization
Vector)是广大工作形式中用来随机化加密的一块数据,因而可以由同样的当众,相同的密钥爆发不同的密文,而无需重新产生密钥,避免了常见卓殊复杂的这一经过。

最先化向量与密钥相比较有例外的安全性要求,由此IV日常并非保密,但是在多数意况中,不应有在应用同一密钥的情况下五遍采纳同一个IV。对于CBC和CFB,重用IV会导致泄露明文首个块的少数音信,亦包括多少个不同音讯中千篇一律的前缀。对于OFB和CTR而言,重用IV会导致全盘失去安全性。此外,在CBC形式中,IV在加密时务必是无力回天臆度的;特其它,在无数贯彻中利用的发生IV的主意,例如SSL2.0施用的,即接纳上一个信息的最后一块密文作为下一个消息的IV,是不安全的。

在意:ECB格局不需要最先化向量,之所以提一句,是因为自己用的ECB格局。

填充

块密码只好对确定长度的多少块举办拍卖,而音信的尺寸一般是可变的。由此有的情势(即ECB和CBC)需要最后一块在加密前举行填写。有数种填充方法,其中最简便的一种是在平文的终极填充空字符以使其尺寸为块长度的平头倍,但必须保证可以苏醒平文的本来面目长度;例如,若平文是C语言风格的字符串,则只有串尾会有空字符。稍微复杂一点的主意则是原本的DES使用的主意,即在数量后添加一个1位,再添加充足的0位直到满意块长度的渴求;若音讯长度刚好符合块长度,则增长一个填充块。最复杂的则是指向CBC的模式,例如密文窃取,残块终结等,不会发生额外的密文,但会增多部分复杂度。布鲁斯(Bruce)·施奈尔和尼尔(Neil)斯·福开森提议了二种简易的可能:添加一个值为128的字节(十六进制的80),再以0字节填满最终一个块;或向终极一个块填充n个值均为n的字节。

CFB,OFB和CTR形式不需要对长度不为密码块大小整数倍的音信举办专门的处理。因为那么些形式是透过对块密码的输出与平文举办异或工作的。最终一个平文块(可能是不完整的)与密钥流块的前多少个字节异或后,暴发了与该平文块大小相同的密文块。流密码的那个特点使得它们得以应用在急需密文和平文数据长度严峻相等的场子,也得以拔取在以流形式传输数据而不便利进行填写的场馆。

只顾:ECB情势是内需填写的。

ECB:
最简便易行的加密情势即为电子密码本(Electronic
codebook,ECB)情势。需要加密的消息按照块密码的块大小被分成数个块,并对每个块举办单独加密。

ECB加密

ECB解密

本办法的通病在于同样的平文块会被加密成相同的密文块;因而,它不可能很好的隐蔽数据情势。在好几场馆,这种形式不可能提供严俊的多太守密性,由此并不推荐用于密码协议中。下边的例证显示了ECB在密文中展现平文的格局的档次:该图像的一个位图版本(上图)通过ECB形式也许会被加密成中图,而非ECB形式通常会将其加密成最下图。

原图

采纳ECB情势加密

提供了伪随机性的非ECB格局

原图是拔取CBC,CTR或任何此外的更安全的格局加密最下图可能发生的结果——与随机噪声无异。注意最下图看起来的随机性并不可以表示图像已经被梧州的加密;许多不安全的加密法也恐怕暴发这种“随机的”输出。

ECB格局也会导致使用它的商议不可能提供数据完整性尊敬,易受到重放攻击的影响,因而各样块是以完全相同的章程解密的。例如,“梦幻之星在线:绿色脉冲”在线电子游戏接纳ECB形式的Blowfish密码。在密钥互换系统被破解而暴发更简便易行的破解格局前,作弊者重复通过发送加密的“杀死怪物”新闻包以私自的快速扩大阅历值。

另外格局在此就不进行了,详情请转块密码的工作格局
,进一步明白CBC、CFB、OFB、CTR等形式。

把最重大的函数摘出来解释一下:

/*!
    @function   CCCrypt
    @abstract   Stateless, one-shot encrypt or decrypt operation.
                This basically performs a sequence of CCCrytorCreate(),
                CCCryptorUpdate(), CCCryptorFinal(), and CCCryptorRelease().

    @param      alg             Defines the encryption algorithm.


    @param      op              Defines the basic operation: kCCEncrypt or
                    kCCDecrypt.

    @param      options         A word of flags defining options. See discussion
                                for the CCOptions type.

    @param      key             Raw key material, length keyLength bytes. 

    @param      keyLength       Length of key material. Must be appropriate 
                                for the select algorithm. Some algorithms may 
                                provide for varying key lengths.

    @param      iv              Initialization vector, optional. Used for 
                                Cipher Block Chaining (CBC) mode. If present, 
                                must be the same length as the selected 
                                algorithm's block size. If CBC mode is
                                selected (by the absence of any mode bits in 
                                the options flags) and no IV is present, a 
                                NULL (all zeroes) IV will be used. This is 
                                ignored if ECB mode is used or if a stream 
                                cipher algorithm is selected. 

    @param      dataIn          Data to encrypt or decrypt, length dataInLength 
                                bytes. 

    @param      dataInLength    Length of data to encrypt or decrypt.

    @param      dataOut         Result is written here. Allocated by caller. 
                                Encryption and decryption can be performed
                                "in-place", with the same buffer used for 
                                input and output. 

    @param      dataOutAvailable The size of the dataOut buffer in bytes.  

    @param      dataOutMoved    On successful return, the number of bytes
                    written to dataOut. If kCCBufferTooSmall is
                returned as a result of insufficient buffer
                space being provided, the required buffer space
                is returned here. 

    @result     kCCBufferTooSmall indicates insufficent space in the dataOut
                                buffer. In this case, the *dataOutMoved 
                                parameter will indicate the size of the buffer
                                needed to complete the operation. The 
                                operation can be retried with minimal runtime 
                                penalty. 
                kCCAlignmentError indicates that dataInLength was not properly 
                                aligned. This can only be returned for block 
                                ciphers, and then only when decrypting or when 
                                encrypting with block with padding disabled. 
                kCCDecodeError  Indicates improperly formatted ciphertext or
                                a "wrong key" error; occurs only during decrypt
                                operations. 
 */  

CCCryptorStatus CCCrypt(
    CCOperation op,         /* 枚举值,确认是加密操作,还是解密操作 */
    CCAlgorithm alg,        /* 枚举值,确认加解密的算法,如kCCAlgorithmAES128、kCCAlgorithmDES */
    CCOptions options,      /* 枚举值,kCCOptionPKCS7Padding | kCCOptionECBMode,经我调查,这样就是ECB模式,并以PKCS7来填充*/
    const void *key,
    size_t keyLength,
    const void *iv,         /* 初始化向量(NULLoptional initialization vector),ECB模式写NULL就行 */
    const void *dataIn,     /* optional per op and alg */
    size_t dataInLength,
    void *dataOut,          /* data RETURNED here */
    size_t dataOutAvailable,
    size_t *dataOutMoved)  

地点说到,iOS和Android填充是不平等的,这怎么做?据说,PKCS7Padding是兼容PKCS5Padding的,我在与安卓联合测试中,确实没有问题。

把自己用的AES加密摘出来吧:

本身用的是一个NSData类目NSData+AES,密钥是128位的,即16个字节,加密解密方法的兑现如下(记得引#import <CommonCrypto/CommonCryptor.h>):

加密:

- (NSData *)AES128EncryptWithKey:(NSString *)key
{
    // 'key' should be 32 bytes for AES256, will be null-padded otherwise
    char keyPtr[kCCKeySizeAES128+1]; // room for terminator (unused)
    bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)

    // fetch key data
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

    NSUInteger dataLength = [self length];

    //See the doc: For block ciphers, the output size will always be less than or
    //equal to the input size plus the size of one block.
    //That's why we need to add the size of one block here
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);

    size_t numBytesEncrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding | kCCOptionECBMode,
                                          keyPtr, kCCKeySizeAES128,
                                          NULL /* initialization vector (optional) */,
                                          [self bytes], dataLength, /* input */
                                          buffer, bufferSize, /* output */
                                          &numBytesEncrypted);
    if (cryptStatus == kCCSuccess) {
        //the returned NSData takes ownership of the buffer and will free it on deallocation
        return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
    }

    free(buffer); //free the buffer;
    return nil;
}  

解密:

- (NSData *)AES128DecryptWithKey:(NSString *)key {
    // 'key' should be 32 bytes for AES256, will be null-padded otherwise
    char keyPtr[kCCKeySizeAES128+1]; // room for terminator (unused)
    bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)

    // fetch key data
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

    NSUInteger dataLength = [self length];

    //See the doc: For block ciphers, the output size will always be less than or
    //equal to the input size plus the size of one block.
    //That's why we need to add the size of one block here
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);

    size_t numBytesDecrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding| kCCOptionECBMode,
                                          keyPtr, kCCKeySizeAES128,
                                          NULL /* initialization vector (optional) */,
                                          [self bytes], dataLength, /* input */
                                          buffer, bufferSize, /* output */
                                          &numBytesDecrypted);

    if (cryptStatus == kCCSuccess) {
        //the returned NSData takes ownership of the buffer and will free it on deallocation
        return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
    }

    free(buffer); //free the buffer;
    return nil;
}  
五个通用队列:
  • 串行队列:所有任务会在一条线程中实施(有可能是现阶段线程也有可能是新开拓的线程),并且一个职责履行完毕后,才起来履行下一个职责。(等待完成)
  • 相互队列:可以打开多条线程并行执行任务(但不肯定会张开新的线程),并且当一个职责放到指定线程最先执行时,下一个任务就足以起头施行了。(等待暴发)

参照小说


8. 一同+串行:未开发新线程,串行执行任务;同步+并行:未开发新线程,串行执行任务;异步+串行:新开拓一条线程,串行执行任务;异步+并行:开辟多条新线程,并行执行任务;在主线程中一块使用主队列执行任务,会招致死锁。

咱俩说说队列与实践措施的陪衬

下边说了系统自带的六个体系,下边大家来用自己创制的系列探讨一下各个搭配情形。
大家先创设五个序列,并且测试方法都是在主线程中调用:

//串行队列
self.serialQueue = dispatch_queue_create("serialQueue.ys.com", DISPATCH_QUEUE_SERIAL);
//并行队列
self.concurrentQueue = dispatch_queue_create("concurrentQueue.ys.com", DISPATCH_QUEUE_CONCURRENT);

事出必有因,前日自己想和你聊聊线程的缘故就是——当然是本着一个共产党人的思想觉悟,为全民透析生命,讲解你正在蒙圈的知识点,或者想破脑袋才发觉这样简约的技巧方案。

重重人学线程,迷迷糊糊;很多少人问线程,有所指望;也有广大人写线程,分享认知给正在竭力的小青年,呦,呦,呦呦。不过,你真的精通线程么?你实在会用多线程么?你实在学领悟,问明了,写清楚了么?不管你明不明了,反正自己不知情,但是,没准,你看完,你就了然了。


5. dispatch_apply

该函数用于重复执行某个任务,就算任务队列是并行队列,重复执行的职责会并发执行,假诺任务队列为串行队列,则任务会相继执行,需要留意的是,该函数为一起函数,要防范线程阻塞和死锁哦,老铁。

GCD

GCD,全名Grand Central Dispatch,中文名郭草地,是依照C语言的一套多线程开发API,一听名字就是个狠角色,也是当下苹果官方推荐的多线程开发形式。可以说是使用方便,又不失逼格。总体来说,他解决自己关系的方面直接操作线程带来的难题,它自动帮你管理了线程的生命周期以及任务的履行规则。下边大家会一再的说道一个词,这就是任务,说白了,任务实际就是你要执行的那段代码

2. 这么不死锁

不如就写个最简便易行的:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1========%@",[NSThread currentThread]);
    NSLog(@"2========%@",[NSThread currentThread]);
    NSLog(@"3========%@",[NSThread currentThread]);
}

打印结果:

ThreadDemo[5803:939324] 1========<NSThread: 0x600000078340>{number = 1, name = main}
ThreadDemo[5803:939324] 2========<NSThread: 0x600000078340>{number = 1, name = main}
ThreadDemo[5803:939324] 3========<NSThread: 0x600000078340>{number = 1, name = main}

事先有人问:顺序打印,没毛病,全在主线程执行,而且顺序执行,这它们必然是在主队列同步施行的哟!那为什么没有死锁?苹果的操作系统果然高深啊!

实际这里有一个误区,这就是任务在主线程顺序执行就是主队列。其实某些关乎都并未,假若当前在主线程,同步施行任务,不管在哪些队列任务都是各种执行。把装有任务皆以异步执行的法子投入到主队列中,你会发现它们也是逐一执行的。

相信您知道地点的死锁状况后,你一定会手贱改成这么试试:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1========%@",[NSThread currentThread]);
    dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    NSLog(@"3========%@",[NSThread currentThread]);
}

打印结果:

ThreadDemo[5830:947858] 1========<NSThread: 0x60000007bb80>{number = 1, name = main}
ThreadDemo[5830:947858] 2========<NSThread: 0x60000007bb80>{number = 1, name = main}
ThreadDemo[5830:947858] 3========<NSThread: 0x60000007bb80>{number = 1, name = main}

你发觉正常执行了,并且是各种执行的,你是不是若有所思,没错,你想的和自己想的是一样的,和上诉情形一模一样,任务A在主队列中,不过任务B插足到了大局队列,这时候,任务A和职责B没有队列的约束,所以任务B就先执行喽,执行完毕之后函数重返,任务A接着执行。

自家猜你早晚手贱这么改过:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1========%@",[NSThread currentThread]);
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    NSLog(@"3========%@",[NSThread currentThread]);
}

打印结果:

ThreadDemo[5911:962470] 1========<NSThread: 0x600000072700>{number = 1, name = main}
ThreadDemo[5911:962470] 3========<NSThread: 0x600000072700>{number = 1, name = main}
ThreadDemo[5911:962470] 2========<NSThread: 0x600000072700>{number = 1, name = main}

仔细而帅气的你一定发现不是各类打印了,而且也不会死锁,明明都是加到主队列里了呀,其实当任务A在履行时,任务B插足到了主队列,注意啊,是异步执行,所以dispatch函数不会等到Block执行到位才回到,dispatch函数重返后,这任务A能够继续执行,Block任务我们可以认为在下一帧顺序进入队列,并且默认无限下一帧执行。这就是为何您看看2===... ...是最终输出的了。(⚠️一个函数的有四个里面函数异步执行时,不会导致死锁的同时,任务A执行完毕后,这些异步执行的里边函数会顺序执行)。

NSOperation

它提供了关于任务的履行,撤销,以及天天得到任务的情事,添加任务依赖以及优先级等办法和特性,绝对于GCD提供的法子来说,更直观,更有利于,并且提供了更多的操纵接口。(很多时候,苹果设计的架构是很棒的,不要只是在乎他促成了何等,可能你学到的事物会更多,一不小心又吹牛逼了,哦呵呵),有多少个办法和总体性大家询问一下:

@interface NSOperation : NSObject {
@private
    id _private;
    int32_t _private1;
#if __LP64__
    int32_t _private1b;
#endif
}

- (void)start;//启动任务 默认在当前线程执行
- (void)main;//自定义NSOperation,写一个子类,重写这个方法,在这个方法里面添加需要执行的操作。

@property (readonly, getter=isCancelled) BOOL cancelled;//是否已经取消,只读
- (void)cancel;//取消任务

@property (readonly, getter=isExecuting) BOOL executing;//正在执行,只读
@property (readonly, getter=isFinished) BOOL finished;//执行结束,只读
@property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below
@property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0);//是否并发,只读
@property (readonly, getter=isReady) BOOL ready;//准备执行

- (void)addDependency:(NSOperation *)op;//添加依赖
- (void)removeDependency:(NSOperation *)op;//移除依赖

@property (readonly, copy) NSArray<NSOperation *> *dependencies;//所有依赖关系,只读

typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};//系统提供的优先级关系枚举

@property NSOperationQueuePriority queuePriority;//执行优先级

@property (nullable, copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0);//任务执行完成之后的回调

- (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0);//阻塞当前线程,等到某个operation执行完毕。

@property double threadPriority NS_DEPRECATED(10_6, 10_10, 4_0, 8_0);//已废弃,用qualityOfService替代。

@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);//服务质量,一个高质量的服务就意味着更多的资源得以提供来更快的完成操作。

@property (nullable, copy) NSString *name NS_AVAILABLE(10_10, 8_0);//任务名称

@end

然而NSOperation自家是个抽象类,不可以直接使用,我们有三种模式予以它新的人命,就是底下这四个东西,您坐稳看好。

互相之间队列:
-(void)GCDApply{
    //重复执行
    dispatch_apply(5, self.concurrentQueue, ^(size_t i) {
        NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
    });
}

运转结果:

ThreadDemo[1461:160567] 第2次_<NSThread: 0x608000076000>{number = 4, name = (null)}
ThreadDemo[1461:160534] 第0次_<NSThread: 0x60800006d8c0>{number = 1, name = main}
ThreadDemo[1461:160566] 第3次_<NSThread: 0x60000007d480>{number = 5, name = (null)}
ThreadDemo[1461:160569] 第1次_<NSThread: 0x60000007d440>{number = 3, name = (null)}
ThreadDemo[1461:160567] 第4次_<NSThread: 0x608000076000>{number = 4, name = (null)}
作业是这么的:

咱俩先做一个定义:- (void)viewDidLoad{} —> 任务A,GCD同步函数
—>任务B。
不问可知呢,大概是这么的,首先,任务A在主队列,并且已经开头施行,在主线程打印出1===... ...,然后这时任务B被投入到主队列中,并且一路施行,这尼玛事都大了,系统说,同步执行啊,这自己不开新的线程了,任务B说自家要等我里面的Block函数执行到位,要不自己就不回来,可是主队列说了,玩蛋去,我是串行的,你得等A执行完才能轮到你,不可能坏了规矩,同时,任务B作为任务A的中间函数,必须等职责B执行完函数重返才能执行下一个任务。这就招致了,任务A等待任务B完成才能继续执行,但作为串行队列的主队列又不可能让任务B在任务A未到位此前起头举行,所以任务A等着任务B完成,任务B等着任务A完成,等待,永久的守候。所以就死锁了。简单不?上边大家郑重看一下我们不知不觉书写的代码!

任务履行办法

说完队列,相应的,任务除了管理,还得执行,要不然有钱不花,掉了徒劳,并且在GCD中并不可能平素开辟线程执行任务,所以在职责插足队列之后,GCD给出了二种实施办法——同步施行(sync)和异步执行(async)。

  • 一齐施行:在最近线程执行任务,不会开发新的线程。必须等到Block函数执行完毕后,dispatch函数才会回去。
  • 异步执行:能够在新的线程中举行任务,但不肯定会开发新的线程。dispatch函数会立马回到,
    然后Block在后台异步执行。
皇君王有木有,跨平台有木有,你没用过有木有!下边我们来看一下那些仿佛牛逼但实在基本用不到的Pthreads是怎么用的:

不如我们来用Pthreads创立一个线程去实施一个任务:

记得引入头文件`#import "pthread.h"`

-(void)pthreadsDoTask{
    /*
     pthread_t:线程指针
     pthread_attr_t:线程属性
     pthread_mutex_t:互斥对象
     pthread_mutexattr_t:互斥属性对象
     pthread_cond_t:条件变量
     pthread_condattr_t:条件属性对象
     pthread_key_t:线程数据键
     pthread_rwlock_t:读写锁
     //
     pthread_create():创建一个线程
     pthread_exit():终止当前线程
     pthread_cancel():中断另外一个线程的运行
     pthread_join():阻塞当前的线程,直到另外一个线程运行结束
     pthread_attr_init():初始化线程的属性
     pthread_attr_setdetachstate():设置脱离状态的属性(决定这个线程在终止时是否可以被结合)
     pthread_attr_getdetachstate():获取脱离状态的属性
     pthread_attr_destroy():删除线程的属性
     pthread_kill():向线程发送一个信号
     pthread_equal(): 对两个线程的线程标识号进行比较
     pthread_detach(): 分离线程
     pthread_self(): 查询线程自身线程标识号
     //
     *创建线程
     int pthread_create(pthread_t _Nullable * _Nonnull __restrict, //指向新建线程标识符的指针
     const pthread_attr_t * _Nullable __restrict,  //设置线程属性。默认值NULL。
     void * _Nullable (* _Nonnull)(void * _Nullable),  //该线程运行函数的地址
     void * _Nullable __restrict);  //运行函数所需的参数
     *返回值:
     *若线程创建成功,则返回0
     *若线程创建失败,则返回出错编号
     */

    //
    pthread_t thread = NULL;
    NSString *params = @"Hello World";
    int result = pthread_create(&thread, NULL, threadTask, (__bridge void *)(params));
    result == 0 ? NSLog(@"creat thread success") : NSLog(@"creat thread failure");
    //设置子线程的状态设置为detached,则该线程运行结束后会自动释放所有资源
    pthread_detach(thread);
}

void *threadTask(void *params) {
    NSLog(@"%@ - %@", [NSThread currentThread], (__bridge NSString *)(params));
    return NULL;
}

输出结果:

ThreadDemo[1197:143578] creat thread success
ThreadDemo[1197:143649] <NSThread: 0x600000262e40>{number = 3, name = (null)} - Hello World

从打印结果来看,该任务是在新开拓的线程中实施的,可是感觉用起来超不自己,很多东西需要自己管理,单单是任务队列以及线程生命周期的管理就够你感冒的,这您写出的代码仍能是方法么!其实之所以放弃那套API很少用,是因为我们有更好的挑选:NSThread

三个独特队列:
  • 主队列:系统为大家创立好的一个串行队列,牛逼之处在于它管理必须在主线程中施行的天职,属于有劳保的。
  • 大局队列:系统为我们制造好的一个交互队列,使用起来与我们团结创造的相互队列无本质差距。
5. 只假如队列,肯定是FIFO(先进先出),不过什么人先进行完要看第1条。

NSOperationQueue

上边说道我们创制的NSOperation任务目的可以通过start主意来实施,同样我们得以把这么些任务目标添加到一个NSOperationQueue对象中去履行,好想有好东西,先看一下类其它API:

@interface NSOperationQueue : NSObject {
@private
    id _private;
    void *_reserved;
}

- (void)addOperation:(NSOperation *)op;//添加任务
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait NS_AVAILABLE(10_6, 4_0);//添加一组任务

- (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);//添加一个block形式的任务

@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;//队列中所有的任务数组
@property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0);//队列中的任务数

@property NSInteger maxConcurrentOperationCount;//最大并发数

@property (getter=isSuspended) BOOL suspended;//暂停

@property (nullable, copy) NSString *name NS_AVAILABLE(10_6, 4_0);//名称

@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);//服务质量,一个高质量的服务就意味着更多的资源得以提供来更快的完成操作。

@property (nullable, assign /* actually retain */) dispatch_queue_t underlyingQueue NS_AVAILABLE(10_10, 8_0);

- (void)cancelAllOperations;//取消队列中的所有任务

- (void)waitUntilAllOperationsAreFinished;//阻塞当前线程,等到队列中的任务全部执行完毕。

#if FOUNDATION_SWIFT_SDK_EPOCH_AT_LEAST(8)
@property (class, readonly, strong, nullable) NSOperationQueue *currentQueue NS_AVAILABLE(10_6, 4_0);//获取当前队列
@property (class, readonly, strong) NSOperationQueue *mainQueue NS_AVAILABLE(10_6, 4_0);//获取主队列
#endif

@end

来一段代码欣然自得喜上眉梢:

-(void)NSOperationQueueRun{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSInvocationOperation *invocationOper = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperSel) object:nil];
    [queue addOperation:invocationOper];
    NSBlockOperation *blockOper = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"NSBlockOperationRun_%@",[NSThread currentThread]);
    }];
    [queue addOperation:blockOper];
    [queue addOperationWithBlock:^{
        NSLog(@"QUEUEBlockOperationRun_%@",[NSThread currentThread]);
    }];
}

打印结果:

ThreadDemo[4761:1205689] NSBlockOperationRun_<NSThread: 0x600000264480>{number = 4, name = (null)}
ThreadDemo[4761:1205691] NSInvocationOperationRun_<NSThread: 0x600000264380>{number = 3, name = (null)}
ThreadDemo[4761:1205706] QUEUEBlockOperationRun_<NSThread: 0x6000002645c0>{number = 5, name = (null)}

俺们发现,参加队列之后并非调用任务的start办法,队列会帮您管理职责的施行情形。上诉执行结果表明这么些任务在队列中为出现执行的。

GCD其他函数用法

2. 串行队列 + 异步执行
-(void)queue_taskTest{
    dispatch_async(self.serialQueue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:1];
    });
    dispatch_async(self.serialQueue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:2];
    });
    dispatch_async(self.serialQueue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:3];
    });
    NSLog(@"4========%@",[NSThread currentThread]);
}

打印结果:

ThreadDemo[6774:1073235] 4========<NSThread: 0x60800006e9c0>{number = 1, name = main}
ThreadDemo[6774:1073290] 1========<NSThread: 0x608000077000>{number = 3, name = (null)}
ThreadDemo[6774:1073290] 2========<NSThread: 0x608000077000>{number = 3, name = (null)}
ThreadDemo[6774:1073290] 3========<NSThread: 0x608000077000>{number = 3, name = (null)}

先打印了4,然后逐一在子线程中打印1,2,3。表达异步执行具有开发新线程的力量,并且串行队列必须等到前一个职责执行完才能起先实施下一个职责,同时,异步执行会使内部函数率先再次回到,不会与正在履行的表面函数暴发死锁。

队列的最大并发数

身为,这么些行列最多可以有稍许任务同时实施,或者说最多开发多少条线程,假设设置为1,这就一遍只可以举办一个职责,可是,不要以为这和GCD的串行队列一样,即便最大并发数为1,队列任务的举行各样依旧取决于很多因素。

6. 万一是串行队列,肯定要等上一个任务履行到位,才能先河下一个职责。不过相互队列当上一个任务起初举办后,下一个职责就可以起来进行。
GCD就先说到这,很多API没有涉嫌到,有趣味的同桌们可以友善去看看,首要的是方法和习惯,而不是你看过多少。

@synchronized

以此也很粗略,有时候也会用到那么些,要传播一个联手对象(一般就是self),然后将你需要加锁的资源放入代码块中,假若该资源有线程正在访问时,会让此外线程等待,间接上代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.sourceArray_m = [NSMutableArray new];
    [_sourceArray_m addObjectsFromArray:@[@"1",@"2",@"3",@"4",@"5",@"6"]];
    [self threadLock];
}
-(void)threadLock{
    for (int i = 0; i < 8; i++) {
        dispatch_async(self.concurrentQueue, ^{
            NSLog(@"%@",[self sourceOut]) ;
        });
    }
}

-(NSString*)sourceOut{
    NSString *source = @"没有了,取光了";
    @synchronized (self) {
        if (_sourceArray_m.count > 0) {
            source = [_sourceArray_m lastObject];
            [_sourceArray_m removeLastObject];
        }
    }
    return source;
}

运作结果:

ThreadDemo[5625:1301834] 5
ThreadDemo[5625:1301835] 6
ThreadDemo[5625:1301837] 4
ThreadDemo[5625:1301852] 3
ThreadDemo[5625:1301834] 1
ThreadDemo[5625:1301854] 2
ThreadDemo[5625:1301835] 没有了,取光了
ThreadDemo[5625:1301855] 没有了,取光了

NSOperation自定义子类

那是自身要说的第一个任务项目,我们可以自定义继承于NSOperation的子类,一视同仁写父类提供的点子,实现一波兼有非凡意义的天职。比如我们去下载一个图纸:

.h
#import <UIKit/UIKit.h>

@protocol YSImageDownLoadOperationDelegate <NSObject>
-(void)YSImageDownLoadFinished:(UIImage*)image;

@end

@interface YSImageDownLoadOperation : NSOperation

-(id)initOperationWithUrl:(NSURL*)imageUrl delegate:(id<YSImageDownLoadOperationDelegate>)delegate;

@end

.m
#import "YSImageDownLoadOperation.h"

@implementation YSImageDownLoadOperation{
    NSURL *_imageUrl;
    id _delegate;
}

-(id)initOperationWithUrl:(NSURL*)imageUrl delegate:(id<YSImageDownLoadOperationDelegate>)delegate{
    if (self == [super init]) {
        _imageUrl = imageUrl;
        _delegate = delegate;
    }
    return self;
}

-(void)main{
    @autoreleasepool {
        UIImage *image = [self imageWithUrl:_imageUrl];
        if (_delegate && [_delegate respondsToSelector:@selector(YSImageDownLoadFinished:)]) {
            [_delegate YSImageDownLoadFinished:image];
        }
    }
}

-(UIImage*)imageWithUrl:(NSURL*)url{
    NSData *imageData = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:imageData];
    return image;
}


@end

然后调用:
-(void)YSDownLoadImageOperationRun{
    YSImageDownLoadOperation *ysOper = [[YSImageDownLoadOperation alloc] initOperationWithUrl:[NSURL URLWithString:@"http://img5.duitang.com/uploads/item/201206/06/20120606174422_LZSeE.thumb.700_0.jpeg"] delegate:self];
    [ysOper start];
}

-(void)YSImageDownLoadFinished:(UIImage *)image{
    NSLog(@"%@",image);
}

运转打印结果:

ThreadDemo[4141:1100329] <UIImage: 0x60800009f630>, {700, 1050}

嗯呵呵,其实自定义的天职更享有指向性,它可以满足你一定的急需,但是一般用的相比较少,不知底是因为自己太菜仍旧真正有不少一发便于的办法和思路实现那样的逻辑。

4. dispatch_barrier_async

栅栏函数,这么看来它能挡住或者分隔什么东西,别瞎猜了,反正你又猜不对,看那,使用此情势创造的任务,会招来当前队列中有没有其他职责要进行,假如有,则等待已有任务执行完毕后再实施,同时,在此任务之后进入队列的职责,需要等待此任务履行到位后,才能举办。看代码,老铁。(⚠️
这里并发队列必须是友好创设的。假若采取全局队列,这一个函数和dispatch_async将会没有差距。)

-(void)GCDbarrier{

    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务1");
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务2");
    });

//    dispatch_barrier_async(self.concurrentQueue, ^{
//        NSLog(@"任务barrier");
//    });

//    NSLog(@"big");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务3");
    });
//    NSLog(@"apple");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务4");
    });
}

运行结果:

ThreadDemo[1816:673351] 任务3
ThreadDemo[1816:673353] 任务1
ThreadDemo[1816:673350] 任务2
ThreadDemo[1816:673370] 任务4

是不是如你所料,牛逼大了,下面我们开拓第一句注释:

-(void)GCDbarrier{

    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务1");
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务2");
    });

    dispatch_barrier_async(self.concurrentQueue, ^{
        NSLog(@"任务barrier");
    });

//    NSLog(@"big");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务3");
    });
//    NSLog(@"apple");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务4");
    });
}

打印结果:

ThreadDemo[1833:678739] 任务2
ThreadDemo[1833:678740] 任务1
ThreadDemo[1833:678740] 任务barrier
ThreadDemo[1833:678740] 任务3
ThreadDemo[1833:678739] 任务4

其一结果和我们地点的讲演完美契合,我们得以概括的主宰函数执行的依次了,你离大牛又近了一步,即便前日的您不会怀疑还有dispatch_barrier_sync其一函数的话,表明…
…嘿嘿嘿,我们看一下这么些函数和地点我们用到的函数的界别,你早晚想到了,再打开第二个和第多少个注释,如下:

-(void)GCDbarrier{

    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务1");
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务2");
    });

    dispatch_barrier_async(self.concurrentQueue, ^{
        NSLog(@"任务barrier");
    });

    NSLog(@"big");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务3");
    });
    NSLog(@"apple");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务4");
    });
}

运转结果:

ThreadDemo[1853:692434] 任务1
ThreadDemo[1853:692421] 任务2
ThreadDemo[1853:692387] big
ThreadDemo[1853:692421] 任务barrier
ThreadDemo[1853:692387] apple
ThreadDemo[1853:692421] 任务3
ThreadDemo[1853:692434] 任务4

毫无焦躁,大家换一下函数:

-(void)GCDbarrier{

    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务1");
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务2");
    });

    dispatch_barrier_sync(self.concurrentQueue, ^{
        NSLog(@"任务barrier");
    });

    NSLog(@"big");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务3");
    });
    NSLog(@"apple");
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"任务4");
    });
}

打印结果:

ThreadDemo[1874:711841] 任务1
ThreadDemo[1874:711828] 任务2
ThreadDemo[1874:711793] 任务barrier
ThreadDemo[1874:711793] big
ThreadDemo[1874:711793] apple
ThreadDemo[1874:711828] 任务3
ThreadDemo[1874:711841] 任务4

老铁,发现了吧?这多少个函数对于队列的栅栏功用是一样的,不过对于该函数相对于任何中间函数遵守了最开头说到的联名和异步的平整。你是不是有点懵逼,假若您蒙蔽了,那么请在每一个输出前面打印出目前的线程,假诺您要么懵逼,那么请你再度看,有劳,不谢!

死锁:
-(void)GCDApply{
    //重复执行
    dispatch_apply(5, dispatch_get_main_queue(), ^(size_t i) {
        NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
    });
}

运行结果:

4. 异步执行dispatch函数会平昔回到,Block函数我们可以认为它会在下一帧插手队列,并依据所在队列近来的天职境况极其下一帧执行,从而不会阻塞当前外部任务的执行。同时,只有异步执行才有开拓新线程的画龙点睛,然而异步执行不肯定会开发新线程。

NSOperation && NSOperationQueue

假定下边的郭草地假使您学会了,那么这五个东西你也不肯定能学得会!

NSOperation以及NSOperationQueue是苹果对此GCD的卷入,其中呢,NSOperation实在就是大家地点所说的任务,不过这么些类不可能直接使用,大家要用他的多少个子类,NSBlockOperationNSInvocationOperation,而NSOperationQueue啊,其实就是近似于GCD中的队列,用于管理你进入到内部的天职。

丰裕借助关系
-(void)NSOperationQueueRun{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *blockOper_1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 1000; i++) {
            NSLog(@"blockOper_1_%@_%@",@(i),[NSThread currentThread]);
        }
    }];

    NSBlockOperation *blockOper_2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 1000; i++) {
            NSLog(@"blockOper_2_%@_%@",@(i),[NSThread currentThread]);
        }
    }];

    [blockOper_1 addDependency:blockOper_2];
    [queue addOperation:blockOper_1];
    [queue addOperation:blockOper_2];
}

打印结果:

ThreadDemo[5066:1233824] blockOper_2_0_<NSThread: 0x600000078340>{number = 3, name = (null)}
ThreadDemo[5066:1233824] blockOper_2_1_<NSThread: 0x600000078340>{number = 3, name = (null)}
ThreadDemo[5066:1233824] blockOper_2_2_<NSThread: 0x600000078340>{number = 3, name = (null)}
ThreadDemo[5066:1233824] blockOper_2_3_<NSThread: 0x600000078340>{number = 3, name = (null)}
... ...
ThreadDemo[5066:1233824] blockOper_2_999_<NSThread: 0x600000078340>{number = 3, name = (null)}
ThreadDemo[5066:1233822] blockOper_1_0_<NSThread: 0x60000006ae80>{number = 4, name = (null)}
... ...
ThreadDemo[5066:1233822] blockOper_1_997_<NSThread: 0x60000006ae80>{number = 4, name = (null)}
ThreadDemo[5066:1233822] blockOper_1_998_<NSThread: 0x60000006ae80>{number = 4, name = (null)}
ThreadDemo[5066:1233822] blockOper_1_999_<NSThread: 0x60000006ae80>{number = 4, name = (null)}

由此打印结果我们可以看出,添加依赖之后,倚重任务必须等待被看重任务履行完毕之后才会开头实施。⚠️,即使看重任务的预先级再高,也是被依赖任务先举行,同时,和先行级不等,依赖关系不受队列的局限,爱哪哪,只尽管本人依赖于您,这您不可能不先实施完,我才实施。

线程锁

地方到底把多线程操作的法子讲完了,下边说一下线程锁机制。多线程操作是多少个线程并行的,所以一律块资源可能在同一时间被四个线程访问,举烂的例证就是买火车票,在就剩一个座时,假若100个线程同时进入,那么可能上列车时就有人得干仗了。为了保养世界和平,人民平安,所以我们讲一下以此线程锁。大家先实现一段代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.sourceArray_m = [NSMutableArray new];
    [_sourceArray_m addObjectsFromArray:@[@"1",@"2",@"3",@"4",@"5",@"6"]];
    [self threadLock];
}
-(void)threadLock{
    for (int i = 0; i < 8; i++) {
        dispatch_async(self.concurrentQueue, ^{
            NSLog(@"%@",[self sourceOut]) ;
        });
    }
}

-(NSString*)sourceOut{
    NSString *source = @"没有了,取光了";
    if (_sourceArray_m.count > 0) {
        source = [_sourceArray_m lastObject];
        [_sourceArray_m removeLastObject];
    }
    return source;
}

运转打印结果:

ThreadDemo[5540:1291666] 6
ThreadDemo[5540:1291669] 6
ThreadDemo[5540:1291682] 5
ThreadDemo[5540:1291667] 4
ThreadDemo[5540:1291683] 3
ThreadDemo[5540:1291666] 2
ThreadDemo[5540:1291669] 1
ThreadDemo[5540:1291682] 没有了,取光了

大家发现6被取出来两回(因为代码简单,执行效率较快,所以这种情状不实必现,耐心多试四次),这样的话就难堪了,一张票卖了2次,这么恶劣的所作所为是不容许容忍的,所以大家需要公平的马弁——线程锁,大家就讲最直接的三种(以前说的GCD的多多主意同样可以等价于线程锁解决那几个题材):

划一下重要啊

NSThread

哟哎,它面向对象,再去探视苹果提供的API,相比较一下Pthreads,简单明了,人生好像又充满了日光和梦想,大家先来一看一下系统提供给我们的API自然就清楚怎么用了,来来来,我给你注释一下啊:

@interface NSThread : NSObject
//当前线程
@property (class, readonly, strong) NSThread *currentThread;
//使用类方法创建线程执行任务
+ (void)detachNewThreadWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
//判断当前是否为多线程
+ (BOOL)isMultiThreaded;
//指定线程的线程参数,例如设置当前线程的断言处理器。
@property (readonly, retain) NSMutableDictionary *threadDictionary;
//当前线程暂停到某个时间
+ (void)sleepUntilDate:(NSDate *)date;
//当前线程暂停一段时间
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
//退出当前线程
+ (void)exit;
//当前线程优先级
+ (double)threadPriority;
//设置当前线程优先级
+ (BOOL)setThreadPriority:(double)p;
//指定线程对象优先级 0.0~1.0,默认值为0.5
@property double threadPriority NS_AVAILABLE(10_6, 4_0);
//服务质量
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
//线程名称
@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);
//栈区大小
@property NSUInteger stackSize NS_AVAILABLE(10_5, 2_0);
//是否为主线程
@property (class, readonly) BOOL isMainThread NS_AVAILABLE(10_5, 2_0);
//获取主线程
@property (class, readonly, strong) NSThread *mainThread NS_AVAILABLE(10_5, 2_0);
//初始化
- (instancetype)init NS_AVAILABLE(10_5, 2_0) NS_DESIGNATED_INITIALIZER;
//实例方法初始化,需要再调用start方法
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument NS_AVAILABLE(10_5, 2_0);
- (instancetype)initWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
//线程状态,正在执行
@property (readonly, getter=isExecuting) BOOL executing NS_AVAILABLE(10_5, 2_0);
//线程状态,正在完成
@property (readonly, getter=isFinished) BOOL finished NS_AVAILABLE(10_5, 2_0);
//线程状态,已经取消
@property (readonly, getter=isCancelled) BOOL cancelled NS_AVAILABLE(10_5, 2_0);
//取消,仅仅改变线程状态,并不能像exist一样真正的终止线程
- (void)cancel NS_AVAILABLE(10_5, 2_0);
//开始
- (void)start NS_AVAILABLE(10_5, 2_0);
//线程需要执行的代码,一般写子类的时候会用到
- (void)main NS_AVAILABLE(10_5, 2_0);
@end

另外,还有一个NSObject的分类,瞅一眼:
@interface NSObject (NSThreadPerformAdditions)
//隐式的创建并启动线程,并在指定的线程(主线程或子线程)上执行方法。
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg NS_AVAILABLE(10_5, 2_0);
@end

下面的介绍您还看中吗?小的帮您下载一张图片,您瞧好:

-(void)creatBigImageView{
    self.bigImageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:_bigImageView];
    UIButton *startButton = [UIButton buttonWithType:UIButtonTypeSystem];
    startButton.frame = CGRectMake(0, 0, self.view.frame.size.width / 2, 50);
    startButton.backgroundColor = [UIColor grayColor];
    [startButton setTitle:@"开始加载" forState:UIControlStateNormal];
    [startButton addTarget:self action:@selector(loadImage) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:startButton];

    UIButton *jamButton = [UIButton buttonWithType:UIButtonTypeSystem];
    jamButton.frame = CGRectMake(self.view.frame.size.width / 2, 0, self.view.frame.size.width / 2, 50);
    jamButton.backgroundColor = [UIColor grayColor];
    [jamButton setTitle:@"阻塞测试" forState:UIControlStateNormal];
    [jamButton addTarget:self action:@selector(jamTest) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:jamButton];
}

-(void)jamTest{
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"线程阻塞" message:@"" delegate:nil cancelButtonTitle:@"好" otherButtonTitles:nil, nil];
    [alertView show];
}


-(void)loadImage{
    NSURL *imageUrl = [NSURL URLWithString:@"http://img5.duitang.com/uploads/item/201206/06/20120606174422_LZSeE.thumb.700_0.jpeg"];
    NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
    [self updateImageData:imageData];
}

-(void)updateImageData:(NSData*)imageData{
    UIImage *image = [UIImage imageWithData:imageData];
    self.bigImageView.image = image;
}

运作结果:

俺们可以明白的收看,主线程阻塞了,用户不得以拓展其他操作,你见过这么的使用吗?
之所以大家这样改一下:

-(void)creatBigImageView{
    self.bigImageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:_bigImageView];
    UIButton *startButton = [UIButton buttonWithType:UIButtonTypeSystem];
    startButton.frame = CGRectMake(0, 20, self.view.frame.size.width / 2, 50);
    startButton.backgroundColor = [UIColor grayColor];
    [startButton setTitle:@"开始加载" forState:UIControlStateNormal];
    [startButton addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:startButton];

    UIButton *jamButton = [UIButton buttonWithType:UIButtonTypeSystem];
    jamButton.frame = CGRectMake(self.view.frame.size.width / 2, 20, self.view.frame.size.width / 2, 50);
    jamButton.backgroundColor = [UIColor grayColor];
    [jamButton setTitle:@"阻塞测试" forState:UIControlStateNormal];
    [jamButton addTarget:self action:@selector(jamTest) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:jamButton];
}

-(void)jamTest{
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"阻塞测试" message:@"" delegate:nil cancelButtonTitle:@"好" otherButtonTitles:nil, nil];
    [alertView show];
}

-(void)loadImageWithMultiThread{
    //方法1:使用对象方法
    //NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage) object:nil];
    //⚠️启动一个线程并非就一定立即执行,而是处于就绪状态,当CUP调度时才真正执行
    //[thread start];

    //方法2:使用类方法
    [NSThread detachNewThreadSelector:@selector(loadImage) toTarget:self withObject:nil];
}

-(void)loadImage{
    NSURL *imageUrl = [NSURL URLWithString:@"http://img5.duitang.com/uploads/item/201206/06/20120606174422_LZSeE.thumb.700_0.jpeg"];
    NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
    //必须在主线程更新UI,Object:代表调用方法的参数,不过只能传递一个参数(如果有多个参数请使用对象进行封装),waitUntilDone:是否线程任务完成执行
    [self performSelectorOnMainThread:@selector(updateImageData:) withObject:imageData waitUntilDone:YES];

    //[self updateImageData:imageData];
}


-(void)updateImageData:(NSData*)imageData{
    UIImage *image = [UIImage imageWithData:imageData];
    self.bigImageView.image = image;
}

运行结果:

啊哎,用多线程果然能迎刃而解线程阻塞的题目,并且NSThread也比Pthreads好用,仿佛你对了解熟习运用多线程又有了一丝丝晨光。假设我有成百上千不同类此外天职,每个任务之间还有联系和倚重性,你是不是又懵逼了,下边的你是不是认为又白看了,其实开发中本身觉着NSThread用到最多的就是[NSThread currentThread];了。(不要慌,往下看…
…)


1. dispatch_after

该函数用于任务延时执行,其中参数dispatch_time_t意味着延时时长,dispatch_queue_t意味着行使哪个队列。如果队列未主队列,那么任务在主线程执行,假若队列为全局队列或者自己创建的系列,那么任务在子线程执行,代码如下:

-(void)GCDDelay{
    //主队列延时
    dispatch_time_t when_main = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC));
    dispatch_after(when_main, dispatch_get_main_queue(), ^{
        NSLog(@"main_%@",[NSThread currentThread]);
    });
    //全局队列延时
    dispatch_time_t when_global = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC));
    dispatch_after(when_global, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"global_%@",[NSThread currentThread]);
    });
    //自定义队列延时
    dispatch_time_t when_custom = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));
    dispatch_after(when_custom, self.serialQueue, ^{
        NSLog(@"custom_%@",[NSThread currentThread]);
    });
}

打印结果:

ThreadDemo[1508:499647] main_<NSThread: 0x60000007cf40>{number = 1, name = main}
ThreadDemo[1508:499697] global_<NSThread: 0x608000262d80>{number = 3, name = (null)}
ThreadDemo[1508:499697] custom_<NSThread: 0x608000262d80>{number = 3, name = (null)}
8. 对此多核CPU来说,线程数量也不可能无限开拓,线程的开发同样会损耗资源,过多线程同时处理任务并不是你想像中的人多力量大。
关于NSOperationQueue还有撤废啊,暂停啊等操作办法,我们可以试一下,应该小心的是,和上学GCD的点子不同,不要连续站在面向过程的角度看带那些面向对象的类,因为它的面貌对象化的包装过程中,肯定有众多您看不到的外貌过程的操作,所以你也从未必要用利用GCD的盘算来套用它,否则你可能会眩晕的一塌糊涂。

NSInvocationOperation

其两个,就是它了,同样也是系统提供给我们的一个任务类,基于一个target对象以及一个selector来创立任务,具体代码:

-(void)NSInvocationOperationRun{
    NSInvocationOperation *invocationOper = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperSel) object:nil];
    [invocationOper start];
}
-(void)invocationOperSel{
    NSLog(@"NSInvocationOperationRun_%@",[NSThread currentThread]);
}

运行结果:

ThreadDemo[4538:1173118] NSInvocationOperationRun_<NSThread: 0x60800006e900>{number = 1, name = main}

运作结果与NSBlockOperation单个block函数的实施办法相同,同步顺序执行。的确系统的包装给予我们关于任务更直观的东西,然则对于六个任务的控制机制并不完美,所以大家有请下一位,也许你会美观。

任务队列组合措施

信任这么些题目你看过许多次?是不是看完也不精通究竟怎么用?这么巧,我也是,请相信下边那么些自然有你不明了并且想要的,我们从四个最直白的点切入:

NSBlockOperation

其次个,就是系统提供的NSOperation的子类NSBlockOperation,我们看一下她提供的API:

@interface NSBlockOperation : NSOperation {
@private
    id _private2;
    void *_reserved2;
}

+ (instancetype)blockOperationWithBlock:(void (^)(void))block;

- (void)addExecutionBlock:(void (^)(void))block;
@property (readonly, copy) NSArray<void (^)(void)> *executionBlocks;

@end

很粗略,就这些,大家就用它实现一个职责:

-(void)NSBlockOperationRun{
    NSBlockOperation *blockOper = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"NSBlockOperationRun_%@_%@",[NSOperationQueue currentQueue],[NSThread currentThread]);
    }];
    [blockOper start];
}

运行结果:

ThreadDemo[4313:1121900] NSBlockOperationRun_<NSOperationQueue: 0x608000037420>{name = 'NSOperationQueue Main Queue'}_<NSThread: 0x60000006dd80>{number = 1, name = main}

咱俩发现这一个职责是在脚下线程顺序执行的,我们发现还有一个格局addExecutionBlock:试一下:

-(void)NSBlockOperationRun{
    NSBlockOperation *blockOper = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"NSBlockOperationRun_1_%@",[NSThread currentThread]);
    }];
    [blockOper addExecutionBlock:^{
        NSLog(@"NSBlockOperationRun_2_%@",[NSThread currentThread]);
    }];
    [blockOper addExecutionBlock:^{
        NSLog(@"NSBlockOperationRun_3_%@",[NSThread currentThread]);
    }];
    [blockOper addExecutionBlock:^{
        NSLog(@"NSBlockOperationRun_4_%@",[NSThread currentThread]);
    }];
    [blockOper start];
}

打印结果:

ThreadDemo[4516:1169835] NSBlockOperationRun_1_<NSThread: 0x60000006d880>{number = 1, name = main}
ThreadDemo[4516:1169875] NSBlockOperationRun_3_<NSThread: 0x600000070800>{number = 4, name = (null)}
ThreadDemo[4516:1169877] NSBlockOperationRun_4_<NSThread: 0x6080000762c0>{number = 5, name = (null)}
ThreadDemo[4516:1169893] NSBlockOperationRun_2_<NSThread: 0x608000076100>{number = 3, name = (null)}

从打印结果来看,那个4个任务是异步并发执行的,开辟了多条线程。

上边的那一个理论都是自我在诸多被套路背后总括出来的血淋淋的阅历,与君共享,可是这样写我猜你早晚仍旧不了然,往下看,说不定有悲喜吗。
3. dispatch_group_async & dispatch_group_notify

试想,现在牛逼的您要现在两张小图,并且你要等两张图都下载完成将来把她们拼起来,你要如何做?我历来就不会把两张图拼成一张图啊,牛逼的本身怎么可能有这种想法啊?

骨子里方法有成千上万,比如您可以一张一张下载,再例如动用部分变量和Blcok实现计数,不过既然前些天大家讲到这,这我们就得入乡随俗,用GCD来实现,有一个神器的东西叫做队列组,当进入到队列组中的所有任务履行到位以后,会调用dispatch_group_notify函数通告任务总体到位,代码如下:

-(void)GCDGroup{
    //
    [self jointImageView];
    //
    dispatch_group_t group = dispatch_group_create();
    __block UIImage *image_1 = nil;
    __block UIImage *image_2 = nil;
    //在group中添加一个任务
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        image_1 = [self imageWithPath:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1502706256731&di=371f5fd17184944d7e2b594142cd7061&imgtype=0&src=http%3A%2F%2Fimg4.duitang.com%2Fuploads%2Fitem%2F201605%2F14%2F20160514165210_LRCji.jpeg"];

    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        image_2 = [self imageWithPath:@"https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=776127947,2002573948&fm=26&gp=0.jpg"];
    });
    //group中所有任务执行完毕,通知该方法执行
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        self.imageView_1.image = image_1;
        self.imageView_2.image = image_2;
        //
        UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 100), NO, 0.0f);
        [image_2 drawInRect:CGRectMake(0, 0, 100, 100)];
        [image_1 drawInRect:CGRectMake(100, 0, 100, 100)];
        UIImage *image_3 = UIGraphicsGetImageFromCurrentImageContext();
        self.imageView_3.image = image_3;
        UIGraphicsEndImageContext();
    });
}

-(void)jointImageView{
    self.imageView_1 = [[UIImageView alloc] initWithFrame:CGRectMake(20, 50, 100, 100)];
    [self.view addSubview:_imageView_1];

    self.imageView_2 = [[UIImageView alloc] initWithFrame:CGRectMake(140, 50, 100, 100)];
    [self.view addSubview:_imageView_2];

    self.imageView_3 = [[UIImageView alloc] initWithFrame:CGRectMake(20, 200, 200, 100)];
    [self.view addSubview:_imageView_3];

    self.imageView_1.layer.borderColor = self.imageView_2.layer.borderColor = self.imageView_3.layer.borderColor = [UIColor grayColor].CGColor;
    self.imageView_1.layer.borderWidth = self.imageView_2.layer.borderWidth = self.imageView_3.layer.borderWidth = 1;
}
3. 联袂执行会在当下线程执行任务,不富有开发线程的能力或者说没有必要开辟新的线程。并且,同步执行必须等到Block函数执行完毕,dispatch函数才会回去,从而阻塞同一串行队列中外部方法的实施。
串行队列:
-(void)GCDApply{
    //重复执行
    dispatch_apply(5, self.serialQueue, ^(size_t i) {
        NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
    });
}

运作结果:

ThreadDemo[1446:158101] 第0次_<NSThread: 0x600000079ac0>{number = 1, name = main}
ThreadDemo[1446:158101] 第1次_<NSThread: 0x600000079ac0>{number = 1, name = main}
ThreadDemo[1446:158101] 第2次_<NSThread: 0x600000079ac0>{number = 1, name = main}
ThreadDemo[1446:158101] 第3次_<NSThread: 0x600000079ac0>{number = 1, name = main}
ThreadDemo[1446:158101] 第4次_<NSThread: 0x600000079ac0>{number = 1, name = main}
2. 线程可以大概的以为就是一段代码+运行时数据。

Pthreads && NSThread

先来看与线程有最直白关乎的一套C的API:

NSLock

代码这样写:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.lock = [[NSLock alloc] init];
    self.sourceArray_m = [NSMutableArray new];
    [_sourceArray_m addObjectsFromArray:@[@"1",@"2",@"3",@"4",@"5",@"6"]];
    [self threadLock];
}
-(void)threadLock{
    for (int i = 0; i < 8; i++) {
        dispatch_async(self.concurrentQueue, ^{
            NSLog(@"%@",[self sourceOut]) ;
        });
    }
}
-(NSString*)sourceOut{
    NSString *source = @"没有了,取光了";
    [_lock lock];
    if (_sourceArray_m.count > 0) {
        source = [_sourceArray_m lastObject];
        [_sourceArray_m removeLastObject];
    }
    [_lock unlock];
    return source;
}

运转结果:

ThreadDemo[5593:1298144] 5
ThreadDemo[5593:1298127] 6
ThreadDemo[5593:1298126] 4
ThreadDemo[5593:1298129] 3
ThreadDemo[5593:1298146] 2
ThreadDemo[5593:1298144] 1
ThreadDemo[5593:1298127] 没有了,取光了
ThreadDemo[5593:1298147] 没有了,取光了

这般就确保了被Lock的资源只好同时让一个线程进行访问,从而也就保证了线程安全。

前言

  • 论及线程,这就只可以提CPU,现代的CPU有一个很要紧的特点,就是时刻片,每一个得到CPU的职责只好运行一个岁月片规定的时间。
  • 事实上线程对操作系统来说就是一段代码以及运行时数据。操作系统会为各类线程保存有关的数码,当接收到来自CPU的时间片中断事件时,就会按自然规则从这一个线程中精选一个,恢复生机它的周转时数据,这样CPU就可以继续执行这一个线程了。
  • 也就是实际就单核CUP而言,并没有主意落实真正意义上的产出执行,只是CPU连忙地在多条线程之间调度,CPU调度线程的年华充裕快,就导致了多线程并发执行的假象。并且就单核CPU而言多线程可以化解线程阻塞的问题,但是其本身运行功能并没有加强,多CPU的互相运算才真正解决了运转效用问题。
  • 系统中正在运行的每一个应用程序都是一个经过,每个过程系统都会分配给它独立的内存运行。也就是说,在iOS系统中中,每一个使用都是一个进程。
  • 一个过程的享有任务都在线程中展开,因而每个过程至少要有一个线程,也就是主线程。这多线程其实就是一个历程开启多条线程,让抱有任务并发执行。
  • 多线程在大势所趋意义上实现了经过内的资源共享,以及功能的晋级。同时,在自然水准上相对独立,它是程序执行流的小不点儿单元,是经过中的一个实体,是实施顺序最核心的单元,有谈得来栈和寄存器。
  • 地方那一个你是不是都通晓,不过我偏要说,哦呵呵。既然大家聊线程,那我们就先从线程开刀。

1. 线程死锁

其一您是不是也看过很多次?哈哈哈!你是不是认为自身又要开头复制黏贴了?请往下看:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1========%@",[NSThread currentThread]);
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    NSLog(@"3========%@",[NSThread currentThread]);
}

运行结果:

打印结果:

ThreadDemo[5615:874679] 1========<NSThread: 0x608000072440>{number = 1, name = main}

真不是自家套路你,大家仍旧得分析一下为啥会死锁,因为必须为那个从没遭到过套路的民情里留下一段美好的回忆,分享代码,我们是认真的!

4. 并行队列 + 异步执行
-(void)queue_taskTest{
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:1];
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:2];
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:3];
    });
    NSLog(@"4========%@",[NSThread currentThread]);
}

打印结果:

ThreadDemo[7042:1117492] 1========<NSThread: 0x600000071900>{number = 3, name = (null)}
ThreadDemo[7042:1117491] 3========<NSThread: 0x608000070240>{number = 5, name = (null)}
ThreadDemo[7042:1117451] 4========<NSThread: 0x600000067400>{number = 1, name = main}
ThreadDemo[7042:1117494] 2========<NSThread: 0x600000071880>{number = 4, name = (null)}

开辟了三个线程,触发任务的时机是逐一的,可是我们看出完成任务的年月却是随机的,这有赖于CPU对于不同线程的调度分配,然则,线程不是无偿无限开拓的,当任务量充分大时,线程是会再一次利用的。

Pthreads

POSIX线程(POSIX
threads),简称Pthreads,是线程的POSIX标准。该规范定义了创办和操纵线程的一整套API。在类Unix操作系统(Unix、Linux、Mac
OS X等)中,都利用Pthreads作为操作系统的线程。

3. 并行队列 + 同步实施
-(void)queue_taskTest{
    dispatch_sync(self.concurrentQueue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:1];
    });
    dispatch_sync(self.concurrentQueue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:2];
    });
    dispatch_sync(self.concurrentQueue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:3];
    });
    NSLog(@"4========%@",[NSThread currentThread]);
}

运转结果:

ThreadDemo[7012:1113594] 1========<NSThread: 0x60800007e340>{number = 1, name = main}
ThreadDemo[7012:1113594] 2========<NSThread: 0x60800007e340>{number = 1, name = main}
ThreadDemo[7012:1113594] 3========<NSThread: 0x60800007e340>{number = 1, name = main}
ThreadDemo[7012:1113594] 4========<NSThread: 0x60800007e340>{number = 1, name = main}

未开启新的线程执行任务,并且Block函数执行到位后dispatch函数才会回到,才能连续向下执行,所以我们看来的结果是各类打印的。

6. dispatch_semaphore_create & dispatch_semaphore_signal & dispatch_semaphore_wait

看那么些函数的时候你需要抛开队列,丢掉同步异步,不要把它们想到一起,混为一谈,信号量只是决定任务执行的一个准绳而已,相对于地点通过队列以及实施情势来控制线程的开拓和职责的履行,它更近乎对于任务一贯的控制。类似于单个体系的最大并发数的操纵机制,提高并行功效的同时,也制止太多线程的开辟对CPU早层负面的效用负担。
dispatch_semaphore_create开创信号量,最先值无法小于0;
dispatch_semaphore_wait等待降低信号量,也就是信号量-1;
dispatch_semaphore_signal进步信号量,也就是信号量+1;
dispatch_semaphore_waitdispatch_semaphore_signal司空见惯配对运用。
看一下代码吧,老铁。

-(void)GCDSemaphore{
    //
    //dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    dispatch_apply(5, self.concurrentQueue, ^(size_t i) {
        //dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(self.concurrentQueue, ^{
            NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
            //dispatch_semaphore_signal(semaphore);
        });
    });
}

你能猜到运行结果吗?没错,就是您想的这么,开辟了5个线程执行任务。

ThreadDemo[1970:506692] 第0次_<NSThread: 0x600000070f00>{number = 3, name = (null)}
ThreadDemo[1970:506711] 第1次_<NSThread: 0x6000000711c0>{number = 4, name = (null)}
ThreadDemo[1970:506713] 第2次_<NSThread: 0x6000000713c0>{number = 5, name = (null)}
ThreadDemo[1970:506691] 第3次_<NSThread: 0x600000070f40>{number = 6, name = (null)}
ThreadDemo[1970:506694] 第4次_<NSThread: 0x600000070440>{number = 7, name = (null)}

下一步你一定猜到了,把注释的代码打开:

-(void)GCDSemaphore{
    //
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    dispatch_apply(5, self.concurrentQueue, ^(size_t i) {
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(self.concurrentQueue, ^{
            NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
            dispatch_semaphore_signal(semaphore);
        });
    });
}

运转结果:

ThreadDemo[2020:513651] 第0次_<NSThread: 0x608000073900>{number = 3, name = (null)}
ThreadDemo[2020:513651] 第1次_<NSThread: 0x608000073900>{number = 3, name = (null)}
ThreadDemo[2020:513651] 第2次_<NSThread: 0x608000073900>{number = 3, name = (null)}
ThreadDemo[2020:513651] 第3次_<NSThread: 0x608000073900>{number = 3, name = (null)}
ThreadDemo[2020:513651] 第4次_<NSThread: 0x608000073900>{number = 3, name = (null)}

很显然,我起来说的是对的,哈哈哈哈,信号量是决定任务履行的严重性尺度,当信号量为0时,所有任务等待,信号量越大,允许可并行执行的职责数量越多。

结语

由此看来该为止了!!!就到这吗,四弟已经竭尽全力了,带我们入个门,这条路表弟只可以陪你走到这了。

1. 串行队列 + 同步实施
-(void)queue_taskTest{
    dispatch_sync(self.serialQueue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:1];
    });
    dispatch_sync(self.serialQueue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:2];
    });
    dispatch_sync(self.serialQueue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:3];
    });
    NSLog(@"4========%@",[NSThread currentThread]);
}

打印结果:

ThreadDemo[6735:1064390] 1========<NSThread: 0x600000073cc0>{number = 1, name = main}
ThreadDemo[6735:1064390] 2========<NSThread: 0x600000073cc0>{number = 1, name = main}
ThreadDemo[6735:1064390] 3========<NSThread: 0x600000073cc0>{number = 1, name = main}
ThreadDemo[6735:1064390] 4========<NSThread: 0x600000073cc0>{number = 1, name = main}

成套都在眼前线程顺序执行,也就是说,同步实施不享有开发新线程的能力。

2. dispatch_once

确保函数在漫天生命周期内只会进行两次,看代码。

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
}

打印结果:

ThreadDemo[1524:509261] <NSThread: 0x600000262940>{number = 1, name = main}
无论你怎么疯狂的点击,在第一次打印之后,输出台便岿然不动。

任务管理格局——队列

下边说当我们要保管五个任务时,线程开发给大家带来了自然的技术难度,或者说不方便性,GCD给出了俺们联合管理任务的方法,那就是队列。大家来看一下iOS多线程操作中的队列:(⚠️不管是串行依旧并行,队列都是听从FIFO的尺度依次触发任务)

1. 对于单核CPU来说,不存在真正含义上的并行,所以,多线程执行任务,其实也只是一个人在劳作,CPU的调度控制了非等待任务的推行速率,同时对于非等待任务,多线程并没有当真意义提升效能。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图