中級
一、Block
1.1 block的實質是什么?一共有幾種block?都是什么情況下生成的?
block對象就是一個結構體,里面有isa指針指向自己的類(global malloc stack),有desc結構體描述block的信息,__forwarding指向自己或堆上自己的地址,如果block對象截獲變量,這些變量也會出現(xiàn)在block結構體中。最重要的block結構體有一個函數(shù)指針,指向block代碼塊。block結構體的構造函數(shù)的參數(shù),包括函數(shù)指針,描述block的結構體,自動截獲的變量(全局變量不用截獲),引用到的__block變量。(__block對象也會轉變成結構體)

int main() {
int (^blk)(int i) = ^(int i){
int result = i + 1;
return result;
};
blk(3);
return 0;
}
//==============上面是反編譯以前的代碼==================
struct __block_impl {
void *isa;//isa表明結構體類型。
int Flags;
int Reserved;
void *FuncPtr;//指向函數(shù)指針
};
//這個結構體及時Block反編譯以后生成的主要結構。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//初始化函數(shù)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;//表示這個Block是存儲于棧上。
impl.Flags = flags;
impl.FuncPtr = fp;//函數(shù)指針賦值
Desc = desc;
}
};
//這個函數(shù)就是Block的具體實現(xiàn),并且添加了一個默認實現(xiàn)。
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int i) {
int result = i + 1;
return result;
}
//Block的描述信息
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
};
//__main_block_desc_0的一個實例,其中Block_size初始化為__main_block_impl_0結構體的大小。
struct __main_block_desc_0 __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main() {
//int (*blk)(int i) = ((int (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
//上面一行轉換為下面兩行等價
struct __main_block_impl_0 tmp = __main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;
//int blkRerurn = ((int (*)(__block_impl *, int))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, 3);
//下面一行是上面一行的簡化版
(*blk->impl.FuncPtr)(blk,3);
return 0;
}
1.2 為什么在默認情況下無法修改被block捕獲的變量? __block都做了什么?
__block變量i在轉換為c語言后直接轉換為一個__Block_byref_i_0類型的結構體
通過i->__forwarding->i獲取外部變量并修改,然后將__main_block_impl_0copy下,再釋放下,具體代碼
1.2.2 為什么block里
1.先定義一個TestObj對象,他的屬性有一個block對象
@interface TestObj : NSObject
@property (nonatomic, copy)void(^block)();
@end
@implementation TestObj
- (void)dealloc {
NSLog(@"%s",func);
} - (instancetype)init {
self = [super init];
if (self) {
__weak typeof(self) weakSelf = self;
self.block = ^{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"%@",weakSelf);
});
};
}
return self;
}
@end
2.再另一個類實例中定義一個testFunc方法
- (void)testFunc{
TestObj *obj = [TestObj new];
obj.block();
}
執(zhí)行testFunc方法,結果是打印的是(null),因為block里打印的方法是異步執(zhí)行的,在 NSLog(@"%@",weakSelf);這句代碼執(zhí)行之前testFunc函數(shù)就結束,所以obj對象已經(jīng)被release了。
1.3 模擬一下循環(huán)引用的一個情況?block實現(xiàn)界面反向傳值如何實現(xiàn)?
二、Runtime
2.1 objc在向一個對象發(fā)送消息時,發(fā)生了什么?
runtime會根據(jù)對象的isa指針找到所對應的類,然后在類的方法列表、父類的方法列表里找對應的方法運行,但是在發(fā)送消息時,objc_msgSend不會返回值,只會在程序實際運行時決定
附注:c語言,調用一個方法其實就是跳到內存中的某一點并開始執(zhí)行一段代碼。沒有任何動態(tài)的特性,因為這在編譯時就決定好了。而在 Objective-C 中,[object foo] 語法并不會立即執(zhí)行 foo 這個方法的代碼。它是在運行時給 object 發(fā)送一條叫 foo 的消息。這個消息,也許會由 object 來處理,也許會被轉發(fā)給另一個對象,或者不予理睬假裝沒收到這個消息。多條不同的消息也可以對應同一個方法實現(xiàn)。這些都是在程序運行的時候決定的。
[array insertObject:foo atIndex:5];
//編譯時轉化成
objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5);
2.2 objc中向一個nil對象發(fā)送消息將會發(fā)生什么?
向一個nil對象發(fā)送消息,首先在尋找對象的isa指針時就是0地址返回了
2.3 什么時候會報unrecognized selector錯誤?iOS有哪些機制來避免走到這一步?
- 對象沒有這個方法,對象有這個方法但是調用時候已經(jīng)釋放
-
respondsToSelector這個方法可以來判斷
image.png
2.4 能否向編譯后得到的類中增加實例變量?能否向運行時創(chuàng)建的類中添加實例變量?為什么?能否添加成員變量?能否添加屬性變量?原理是什么?
- 不能向編譯后得到的類中增加實例變量,因為編譯后的類已經(jīng)注冊在runtime中,objc_ivar_list和instance_size已經(jīng)確定
- 可以向運行時的類中增加實例變量,用
class_addIvar方法,但是必須插在objc_allocateClassPair和objc_registerClassPair之間使用 - 運行時不能添加成員變量,編譯器會報錯
- 可以添加屬性變量
objc_setAssociatedObject,原理如下:
image.png
附注:成員變量、實例變量、屬性變量之間的區(qū)別?成員變量就是用{}括起來的對象,外界無法使用;屬性變量就是@property修飾的,外界可以拿到;實例變量本質上就是成員變量,只是實例對象是針對類,定義了屬性變量myButton會自動生成_myButton,并寫好setter/getter方法
QQ圖片20180310223645.jpg
2.5 runtime如何實現(xiàn)weak變量的自動置nil?
runtime 對注冊的類, 會進行布局,對于 weak 對象會放入一個 hash 表中。 用 weak 指向的對象內存地址作為 key,當此對象的引用計數(shù)為0的時候會 dealloc,假如 weak 指向的對象內存地址是a,那么就會以a為鍵, 在這個 weak 表中搜索,找到所有以a為鍵的 weak 對象,從而設置為 nil。
2.6 給類添加一個屬性后,在類結構體里哪些元素會發(fā)生變化?
objc_ivar_list(實例變量列表)和instance_size(實例內存大?。兓?/p>
2.7 runtime的運用
2.7.1 kvo、kvc實現(xiàn)原理
kvc實現(xiàn)原理
1)檢查是否存在名為-set<key>:的方法,并使用它做設置值
2)如果上述方法不可用,則檢查名為-_<key>、-_is<key>(只針對布爾值有效)、-_get<key>和-_set<key>:方法;
3)如果沒有找到訪問器方法,可以嘗試直接訪問實例變量
4)如果仍為找到,則調用valueForUndefinedKey:和setValue:forUndefinedKey:方法
具體看這里kvo實現(xiàn)原理
自動創(chuàng)建一個原來類的子類,如NSKVONotifying_XX,將原來類的isa指針指向新建的類,重寫setter方法,通過willChangeValueForKey,didChangeValueForKey來檢測屬性值的變化。
removeObserver就是將isa指針指向原來的類
具體demo看這里
2.7.2 category的實現(xiàn)
category內部是如何實現(xiàn)的?與該類原有方法的名稱相同的時候,為什么原有方法會失效?
1. 將Category和它的元類注冊到哈希表
2. 如果元類已經(jīng)實現(xiàn),則重建它的方法列表
2.7.3 添加屬性
objc_setAssociatedObject
2.7.4 添加實例變量
2.7.5 獲取私有方法
2.7.6 替換方法
2.8 + (void)load; 和 + (void)initialize;區(qū)別是什么

Runloop
- runloop是來做什么的?runloop和線程有什么關系?主線程默認開啟了runloop么?子線程呢?
主線程默認開啟了runloop,其他線程默認沒有開啟
具體參考 - runloop的mode是用來做什么的?有幾種mode?
-
NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默認,空閑狀態(tài) -
UITrackingRunLoopMode:ScrollView滑動時 -
UIInitializationRunLoopMode:啟動時 -
NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合
公開的可以用的: - NSDefaultRunLoopMode(kCFRunLoopDefaultMode)
- NSRunLoopCommonModes(kCFRunLoopCommonModes)
-
- 為什么把NSTimer對象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主運行循* 環(huán)以后,滑動scrollview的時候NSTimer卻不動了?
蘋果是如何實現(xiàn)Autorelease Pool的?
其實就是指針組成的堆棧,當要釋放時候,調用objc_autoreleasePoolPush將邊界對象放進AutoreleasePoolPage棧頂,返回返回邊界對象內存地址,接著就是pop操作,對晚于邊界對象的對象發(fā)送release消息,并移動next指針到正確位置
類指針
- isa指針?(對象的isa,類對象的isa,元類的isa都要說)
類方法和實例方法有什么區(qū)別?
- 實例方法
給對象發(fā)送消息,然后在對象的類的方法列表里查找對應的方法 - 類方法
給類發(fā)送消息,然后在類的meta-class的方法列表里查找對應的方法
高級
1.UITableview的優(yōu)化方法(緩存高度,異步繪制,減少層級,hide,避免離屏渲染)
2.有沒有用過runtime,用它都能做什么?(交換方法,創(chuàng)建類,給新創(chuàng)建的類增加方法,改變isa指針)
3.看過哪些第三方框架的源碼?都是如何實現(xiàn)的?(如果沒有,問一下多圖下載的設計)
4.SDWebImage的緩存策略?
5.AFN為什么添加一條常駐線程?
- 為什么要加常駐線程
大量的創(chuàng)建網(wǎng)絡請求線程并銷毀線程會很耗資源 - 為什么把線程加到runloop里
AFN里的網(wǎng)絡請求線程已經(jīng)用單例創(chuàng)建了,會一直存在,為什么還要加到runloop里,是這樣,runloop是需要時候才調用,不需要時候就會“閑置”,比如一個加載小時的頁面,已經(jīng)加載完了,后面基本上不需要網(wǎng)絡請求,那為何一直開著網(wǎng)絡線程呢
6. AsyncDisplayKit原理是什么
內部封裝了UIView、CALayer,是得他們這些屬性都可以在后臺線程設置,從而完成了排版、繪制在后臺線程的實現(xiàn)
造成UI卡頓主要包括:
- 排版:計算視圖大小,計算文本高度,重新計算子視圖的排版
- 繪制:文本繪制、圖片繪制(預先解壓)、元素繪制(Quartz)
- UI對象操作:UIView、CALayer等UI對象的創(chuàng)建、銷毀、屬性設置
8. 關于網(wǎng)絡編程
1.iOS中socket使用
Socket是對TCP/IP協(xié)議的封裝,Socket本身并不是協(xié)議,而是一個調用接口(API),通過Socket,我們才能使用TCP/IP協(xié)議。
http協(xié)議 對應于應用層
tcp協(xié)議 對應于傳輸層
ip協(xié)議 對應于網(wǎng)絡層
三者本質上沒有可比性。 何況HTTP協(xié)議是基于TCP連接的。
TCP/IP是傳輸層協(xié)議,主要解決數(shù)據(jù)如何在網(wǎng)絡中傳輸;而HTTP是應用層協(xié)議,主要解決如何包裝數(shù)據(jù)。
我 們在傳輸數(shù)據(jù)時,可以只使用傳輸層(TCP/IP),但是那樣的話,由于沒有應用層,便無法識別數(shù)據(jù)內容,如果想要使傳輸?shù)臄?shù)據(jù)有意義,則必須使用應用層 協(xié)議,應用層協(xié)議很多,有HTTP、FTP、TELNET等等,也可以自己定義應用層協(xié)議。WEB使用HTTP作傳輸層協(xié)議,以封裝HTTP文本信息,然 后使用TCP/IP做傳輸層協(xié)議將它發(fā)送到網(wǎng)絡上。
SOCKET原理
套接字(socket)概念套接字(socket)是通信的基石,是支持TCP/IP協(xié)議的網(wǎng)絡通信的基本操作單元。它是網(wǎng)絡通信過程中端點的抽象表示,包含進行網(wǎng)絡通信必須的五種信息:連接使用的協(xié)議,本地主機的IP地址,本地進程的協(xié)議端口,遠地主機的IP地址,遠地進程的協(xié)議端口。
應 用層通過傳輸層進行數(shù)據(jù)通信時,TCP會遇到同時為多個應用程序進程提供并發(fā)服務的問題。多個TCP連接或多個應用程序進程可能需要通過同一個 TCP協(xié)議端口傳輸數(shù)據(jù)。為了區(qū)別不同的應用程序進程和連接,許多計算機操作系統(tǒng)為應用程序與TCP/IP協(xié)議交互提供了套接字(Socket)接口。應 用層可以和傳輸層通過Socket接口,區(qū)分來自不同應用程序進程或網(wǎng)絡連接的通信,實現(xiàn)數(shù)據(jù)傳輸?shù)牟l(fā)服務。
建立socket連接建立Socket連接至少需要一對套接字,其中一個運行于客戶端,稱為ClientSocket,另一個運行于服務器端,稱為ServerSocket。
套接字之間的連接過程分為三個步驟:服務器監(jiān)聽,客戶端請求,連接確認。
服務器監(jiān)聽:服務器端套接字并不定位具體的客戶端套接字,而是處于等待連接的狀態(tài),實時監(jiān)控網(wǎng)絡狀態(tài),等待客戶端的連接請求。
客戶端請求:指客戶端的套接字提出連接請求,要連接的目標是服務器端的套接字。為此,客戶端的套接字必須首先描述它要連接的服務器的套接字,指出服務器端套接字的地址和端口號,然后就向服務器端套接字提出連接請求。
連 接確認:當服務器端套接字監(jiān)聽到或者說接收到客戶端套接字的連接請求時,就響應客戶端套接字的請求,建立一個新的線程,把服務器端套接字的描述發(fā)給客戶 端,一旦客戶端確認了此描述,雙方就正式建立連接。而服務器端套接字繼續(xù)處于監(jiān)聽狀態(tài),繼續(xù)接收其他客戶端套接字的連接請求。
SOCKET連接與TCP連接創(chuàng)建Socket連接時,可以指定使用的傳輸層協(xié)議,Socket可以支持不同的傳輸層協(xié)議(TCP或UDP),當使用TCP協(xié)議進行連接時,該Socket連接就是一個TCP連接。
Socket連接與HTTP連接由 于通常情況下Socket連接就是TCP連接,因此Socket連接一旦建立,通信雙方即可開始相互發(fā)送數(shù)據(jù)內容,直到雙方連接斷開。但在實際網(wǎng)絡應用 中,客戶端到服務器之間的通信往往需要穿越多個中間節(jié)點,例如路由器、網(wǎng)關、防火墻等,大部分防火墻默認會關閉長時間處于非活躍狀態(tài)的連接而導致 Socket 連接斷連,因此需要通過輪詢告訴網(wǎng)絡,該連接處于活躍狀態(tài)。
而HTTP連接使用的是“請求—響應”的方式,不僅在請求時需要先建立連接,而且需要客戶端向服務器發(fā)出請求后,服務器端才能回復數(shù)據(jù)。
很 多情況下,需要服務器端主動向客戶端推送數(shù)據(jù),保持客戶端與服務器數(shù)據(jù)的實時與同步。此時若雙方建立的是Socket連接,服務器就可以直接將數(shù)據(jù)傳送給 客戶端;若雙方建立的是HTTP連接,則服務器需要等到客戶端發(fā)送一次請求后才能將數(shù)據(jù)傳回給客戶端,因此,客戶端定時向服務器端發(fā)送連接請求,不僅可以 保持在線,同時也是在“詢問”服務器是否有新的數(shù)據(jù),如果有就將數(shù)據(jù)傳給客戶端。
TCP與UDP
TCP和UDP都是傳輸層的協(xié)議:
TCP是傳輸控制層協(xié)議,是面向連接、可靠的,點對點的;
UDP是用戶數(shù)據(jù)報協(xié)議,是不需要連接、不可靠的,點對多點的;
TCP側重于安全傳輸,UDP側重于快速傳輸。
TCP的三次握手:a、用戶端向服務器發(fā)送syn包,用戶端進入send狀態(tài),服務器等待接收;b、服務器接收到syn包并確認后,發(fā)送一個syn+ack包給用戶端,服務器進入recv狀態(tài);c、用戶端收到服務器的返回的syn+ack包后,再發(fā)送一個確認包ack給服務器,此時用戶端和服務器都進入established狀態(tài),連接成功。
HTTP與HTTPS
http是超文本傳輸協(xié)議,是短連接,用戶端向服務器請求數(shù)據(jù)后,服務器響應,連接斷開。http是應用層面向對象的協(xié)議,它由請求報文和響應報文組成:
請求報文:請求行、請求頭、空行和請求體;
響應報文:響應行、響應頭、響應體。
http有GET和POST兩種請求方式:
GET:請求內容拼接在url的后面,可以通過截取url‘?’后面的內容得到,多個參數(shù)由'&'分隔,賬號和密碼會被明文顯示在url的后面,是不安全的,而且傳輸數(shù)據(jù)較少,不超過1024字節(jié);
POST:請求參數(shù)寫在請求數(shù)據(jù)里面,相對GET是比較安全的,提交數(shù)據(jù)放在http包的包體中,傳輸數(shù)據(jù)較多,理論上沒有上限。
http協(xié)議是基于socket的,http的底層就是socket通信。
https是安全超文本傳輸協(xié)議,是基于http協(xié)議開發(fā)的,s指的是secure安全,它通過是安全套接字層來完成用戶端和服務器端的通信,可以說是http的安全版協(xié)議。
Socket與其他
socket是通信的基石,是支持TCP/IP協(xié)議的基本通信單元。socket是基于TCP/IP的協(xié)議的封裝,它本身并不是一個協(xié)議,而是一個接口API,它包含了傳輸協(xié)議,主機IP、主機進程端口號及服務器IP、服務器進程端口號五大基礎部分組成。
在實際應用中,應用層通過傳輸層通信時,TCP常遇到為多個程序提供并發(fā)服務的問題,這時就會有多個TCP或者多個程序需要用同一個TCP協(xié)議端口傳輸數(shù)據(jù),但是服務器無法區(qū)分是具體是哪個程序,這時就有了socket,應用層和傳輸層可以通過socket抽象層來判斷具體為哪個程序進行通信,提供并發(fā)服務。
socket一般成對出現(xiàn),一個是客戶端ClientScoket,一個是服務端ServiceSocket。socket連接分三步:
服務器監(jiān)聽:ServiceSocket不會規(guī)定客戶端的IP與端口號,服務器是處于等待連接的狀態(tài),監(jiān)聽網(wǎng)絡訪問,等待客戶端連接;
客戶端請求:ClientScoket不僅包含自己的主機IP和進程端口號,還包含服務端的IP和進程端口號,通過服務端的IP和端口號找尋對應的ServiceSocket發(fā)送連接請求;
連接確認:當服務器監(jiān)聽或者說接收到ClientScoket的連接請求時,就響應ClientScoket的請求,并新建一個線程,發(fā)送給客戶端完整的ServiceSocket,一旦客戶端確認此ServiceSocket后,連接完成,ServiceSocket繼續(xù)處于等待狀態(tài),等待其他客戶端進程的連接。
ios中創(chuàng)建socket連接五步走:創(chuàng)建socket、連接服務器、用戶發(fā)送數(shù)據(jù)到服務器、服務器響應數(shù)據(jù)返回用戶、關閉socket。
socket與TCP:
socket是可以支持TCP和UDP協(xié)議的,如果是使用TCP協(xié)議進行連接,那么就是一個TCP連接。
socket與http:
socket一般都是基于TCP的,所以是一個長連接,可以進行通信。但實際應用中應用層向傳輸層通信還需要穿越多個中間節(jié)點,例如路由器、網(wǎng)關、防火墻等,而防火墻一般是默認關閉處于不活躍的連接的,所以需要輪詢服務器,保證連接不被關閉。
http是短連接,服務器響應數(shù)據(jù)后就斷開。而實際應用中,經(jīng)常需要服務端與客戶端保持數(shù)據(jù)實時與同步,這就需要服務器發(fā)送數(shù)據(jù)給客戶端,但http只能讓客戶端先建立連接并請求數(shù)據(jù),服務器才能響應數(shù)據(jù)。而采用socket長連接就不需如此,服務器可以直接將數(shù)據(jù)發(fā)送給客戶端。
9. ReactNative原理
面向對象
三大特征
封裝
讓有些人知道,讓有些人不知道繼承
繼承父輩的所有武功,并對這些武功進行發(fā)揚光大多態(tài)
a) 老子生的兒子也是千差萬別的,這個差別就是多態(tài)
b) 常用手段:
覆蓋(重寫):函數(shù)名和參數(shù)都一樣,只是函數(shù)的實現(xiàn)不一樣
重載:函數(shù)名一樣,參數(shù)不一樣
Obj-C部分
內存管理
assign與weak區(qū)別
weak比assign多一個功能,就是當屬性指向的對象消失時候,weak會讓屬性置為nil,這樣向weak修飾的屬性發(fā)送消息就不會崩潰,而assign則不會,所以不能用assign修飾對象;
為什么要用assign修飾基本數(shù)據(jù)類型?因為基礎數(shù)據(jù)類型一般分配在棧上,棧的內存會由系統(tǒng)自己自動處理,不會造成野指針strong(retain)跟copy區(qū)別
strong指針復制,指向同一塊內存區(qū)域;而copy只是內容復制,新開辟一片內存

自動釋放池
當一個自動釋放池子被銷毀,會對池子里對象發(fā)送一條release消息
- 與線程關系
每個線程對應一個NSAutoreleasePool,當新池子被創(chuàng)建,push進棧,當池子被釋放內存,pop出棧
類與對象
- 靜態(tài)方法(類方法)pk實例方法
a) 靜態(tài)方法
靜態(tài)方法用static修飾,屬于類,不屬于實例,效率高但常駐內存,存在堆上,類方法中不可直接使用實例變量
b) 實例方法
實例方法屬于實例,不常駐內存,存在棧上
Runtime
Obj-C不同于c是一個動態(tài)語言,當程序執(zhí)行[object doSomething]時,會向消息接收者(object)發(fā)送一條消息(doSomething),runtime會根據(jù)消息接收者是否能響應該消息而做出不同的反應。
UIApplication

atomic與nonatomic
atomic
a) 定義:
原子操作(原子性是指事務的一個完整操作,操作成功就提交,反之就回滾. 原子操作就是指具有原子性的操作)
b) 屬性中的使用:
屬性設置成atomic ,意思就是 setter /getter函數(shù)是一個原子操作,如果多線程同時調用setter時,不會出現(xiàn)某一個線程執(zhí)行完setter所有語句之前,另一個線程就開始執(zhí)行setter,相當于函數(shù)頭尾加了鎖,保證了getter和setter存取方法的線程安全,并不能保證整個對象是線程安全的,而且關鍵一點——并發(fā)訪問性能會比較低
c) 如何使用
需要與@synthesize/@dynamic配和使用nonatomic
a) 定義:
非原子操作,一般不需要多線程支持的時候就用它,這樣在 并發(fā)訪問的時候效率會比較高
b) 使用:
在objective-c里面通常對象類型都應該聲明為非原子性的. iOS中程序啟動的時候系統(tǒng)只會自動生成一個單一的主線程.程序在執(zhí)行的時候一般情況下是在同一個線程里面對一個屬性進行操作.兩者在多線程的使用
如果在程序中,我們確定某一個屬性會在多線程中被使用,并且需要做數(shù)據(jù)同步,就必須設置成原子性的;但也可以設置成非原子性的,然后自己在程序中用加鎖之類的來做數(shù)據(jù)同步,據(jù)說,atomic要比nonatomic慢大約20倍。一般如果條件允許,我們可以自己加鎖操作。舉個栗子
//設置屬性name為nonatomic
@property (nonatomic, copy) NSString *name;
//創(chuàng)建兩個線程同時操作屬性name
- (IBAction)onclickAtomic:(id)sender {
WS(weakSelf);
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
weakSelf.name = [weakSelf.name stringByAppendingString:@" will "];
NSLog(@"name:%@", weakSelf.name);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
weakSelf.name = [weakSelf.name stringByAppendingString:@" javion "];
NSLog(@"name:%@", weakSelf.name);
}];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue setName:@"MyQueue"];
[queue addOperations:@[op1, op2] waitUntilFinished:NO];
}
結果果然出現(xiàn)了奇葩情況

于是我把屬性改成了atomic,然后就正常了

通俗的例子:
A想要從自己的帳戶中轉1000塊錢到B的帳戶里。那個從A開始轉帳,到轉帳結束的這一個過程,稱之為一個事務。在這個事務里,要做如下操作:
- 從A的帳戶中減去1000塊錢。如果A的帳戶原來有3000塊錢,現(xiàn)在就變成2000塊錢了。
- 在B的帳戶里加1000塊錢。如果B的帳戶如果原來有2000塊錢,現(xiàn)在則變成3000塊錢了。
如果在A的帳戶已經(jīng)減去了1000塊錢的時候,忽然發(fā)生了意外,比如停電什么的,導致轉帳事務意外終止了,而此時B的帳戶里還沒有增加1000塊錢。那么,我們稱這個操作失敗了,要進行回滾?;貪L就是回到事務開始之前的狀態(tài),也就是回到A的帳戶還沒減1000塊的狀態(tài),B的帳戶的原來的狀態(tài)。此時A的帳戶仍然有3000塊,B的帳戶仍然有2000塊。
我們把這種要么一起成功(A帳戶成功減少1000,同時B帳戶成功增加1000),要么一起失?。ˋ帳戶回到原來狀態(tài),B帳戶也回到原來狀態(tài))的操作叫原子性操作。
如果把一個事務可看作是一個程序,它要么完整的被執(zhí)行,要么完全不執(zhí)行。這種特性就叫原子性。


