最近面試婚瓜,碰到一個好玩的公司沼死,出了一個面試題,ta給我了一張紙倘潜,讓寫出NSArray的enumerateObjectsUsingBlock內(nèi)部怎么實現(xiàn)的。
NSArray * list = @[@"1",@"2",@"3"];
[list enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if([obj isEqualToString:@"2"]){
*stop = YES;
}
}];
這題目漂漂亮亮的寫出來志于,起碼需要以下要求:
1涮因、對block有點理解,要不然寫不出block,哈哈伺绽。
2养泡、對指針有點理解,要不然寫不出來奈应。
3澜掩、對自己足夠自信,對蘋果sdk內(nèi)部實現(xiàn)不能有恐懼钥组。
block晚點再說输硝,先看看指針。
1程梦、指針基礎(chǔ):p点把、*p和&p三者的區(qū)別
指針四元素:
指針的類型
你只要把指針聲明語句里的指針名字去掉,剩下的部分就是這個指針的類型
指針所指向的類型
把指針聲明語句中的指針名字和名字左邊的指針聲明符去掉屿附,剩下的就是指針所指向的類型*
指針的值
指針的值是指針本身存儲的數(shù)值郎逃,這個值將被編譯器當作一個地址,而不是一個一般的數(shù)值挺份。
指針本身所占據(jù)的內(nèi)存區(qū)
指針本身占了多大的內(nèi)存褒翰?用函數(shù)sizeof(指針的類型)測一下就知道了,不同位數(shù)的機器大小不同
NSInteger aaa = 3;
NSLog(@"aaa的內(nèi)存地址====%p",&aaa); //aaa的內(nèi)存地址0x111
NSInteger *bbb = &aaa;
NSLog(@"bbb變量存的內(nèi)容====%p",bbb); //這獲取的就是示意圖中的0x111
NSLog(@"bbb的內(nèi)存地址====%p",&bbb); //這獲取的就是示意圖中的0x222
bbb是一個指針變量的名字匀泊,表示此指針變量指向的內(nèi)存地址优训,如果使用%p來輸出的話,它將是一個16進制數(shù)各聘,從上面的結(jié)果可以看到打印bbb和&aaa的值是一樣
*bbb表示此指針指向的內(nèi)存地址中存放的內(nèi)容揣非,一般是一個和指針類型一致的變量或者常量
&是取地址運算符,&bbb就是取指針bbb的地址躲因;
&bbb和bbb的區(qū)別在于:指針bbb同時也是個變量早敬,既然是變量,編譯器肯定要為其分配內(nèi)存地址大脉,&bbb就表示編譯器為變量bbb分配的內(nèi)存地址搞监;而因為bbb是一個指針變量,這種特殊的身份注定了它要指向另外一個內(nèi)存地址镰矿,程序員按照程序的需要讓它指向一個內(nèi)存地址琐驴,所以bbb表示它指向的內(nèi)存地址。
2、基本數(shù)據(jù)類型棍矛、對象類型
char a = 10;
char *p = &a;
char value = *p;
printf("value的值:%d", value); //輸出結(jié)果:value的值:10
NSString *name = @"solo";
NSLog(@"xxxx的內(nèi)存地址====%p",name); //下圖中solo的內(nèi)存首地址安疗,也是name指針在內(nèi)存中存的內(nèi)容
NSLog(@"name的內(nèi)存地址===%p",&name); //name指針的內(nèi)存地址
NSLog(@"name的description===%@",name); //name的description
對象類型,內(nèi)存分布復雜,結(jié)構(gòu)體是一片內(nèi)存區(qū)域够委。
NSString本身也是一個對象荐类,它不止是char *這些基本類型這么簡單。本質(zhì)上OC的對象是一個結(jié)構(gòu)體,是一片內(nèi)存區(qū)域,我們并沒有方法能直接完整打印出這個結(jié)構(gòu)體茁帽。NSLog遇到%@格式和接收對象作為參數(shù)時玉罐,直接調(diào)用的是對象的description方法。這里與基本數(shù)據(jù)類型的處理是有區(qū)別的潘拨。
3吊输、iOS中多級指針的應(yīng)用
指針在iOS中運用十分廣泛,只是太頻繁沒意識到而已铁追,其實每個實例對象都是指針季蚂。這里說的應(yīng)用是指多級指針的運用。下面這倆貨我面寫代碼會經(jīng)忱攀看到:
NSArray * list = @[@"1",@"2",@"3"];
[list enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"enumerateObjectsUsingBlock===%@",obj);
if([obj isEqualToString:@"2"]){
*stop = YES;
}
}];
NSError *error = nil;
[[NSFileManager defaultManager] moveItemAtPath:@"" toPath:@"" error:& error];
if (error) {
NSLog(@"move failed:%@", [error localizedDescription]);
}
為什么這么寫能修改函數(shù)外面的值扭屁?換個有函數(shù)實現(xiàn)簡單的栗子:
- (void)testBaseData
{
NSInteger aaa = 3;
NSLog(@"函數(shù)前===%p",&aaa);//函數(shù)前===0x7ffeec80fba8
[self getNewCount:&aaa];
NSLog(@"aaa2===%ld",aaa);//aaa2===200
}
-(void)getNewCount:(NSInteger *)countaa
{
NSLog(@"函數(shù)里===%p",countaa);//函數(shù)里===0x7ffeec80fba8
*countaa = 200;
}
基本數(shù)據(jù)類型,會顯得比較簡單涩禀×侠模可以看出函數(shù)前和函數(shù)里指針一樣。
把aaa的內(nèi)存地址艾船,賦值給了指針countaa葵腹,countaa就是函數(shù)外面的aaa。修改countaa的值屿岂,就是修改aaa變量在內(nèi)存中存的值践宴。
- (void)showError
{
NSError *error = nil;
NSLog(@"函數(shù)前==%p", &error);//函數(shù)前==0x7ffee06dfd38
[self handleResponseCode:0 error:&error];
NSLog(@"函數(shù)后值==%@", error);//函數(shù)后值==Error Domain=NSCocoaErrorDomain Code=0 "(null)" UserInfo={code=0}
}
- (void)handleResponseCode:(NSInteger)code error:(NSError **)err
{
NSLog(@"函數(shù)里==%p", err);//函數(shù)里==0x7ffee06dfd30
if (code == 0) {
*err = [NSError errorWithDomain:NSCocoaErrorDomain code:code userInfo:@{@"code":@(code)}];
}
}
對著下面的內(nèi)存示意圖分析下:
error是個指針,開始指向nil,在調(diào)用下面的函數(shù)的時候,通過取地符&爷怀,把error的內(nèi)存地址(0x222)賦值給了新的指針變量err浴井。
在函數(shù)中,*err就是err指針指向的變量(就是函數(shù)外面的error)霉撵。修改 *err的指針指向就是修改函數(shù)外error的指針指向。
注意:這里函數(shù)外的內(nèi)存地址0x7ffee06dfd38和函數(shù)里面0x7ffee06dfd30會有略微不同洪囤,是__autoreleasing搞的鬼徒坡,暫且忽略,具體看這個吧
結(jié)論:我們通過一個指針參數(shù)作為橋梁瘤缩,成功修改了函數(shù)外面變量的值喇完。
這么寫到底有啥好處?誰也不會沒事?lián)蔚陌。氵@么個幺蛾子锦溪。
看個栗子:
- (void)testManyParameter
{
NSString * name = @"solo";
NSInteger age = 18;
NSString * money = @"100W";
BOOL isRichGuy = YES;
CGFloat degress = 0.55;
[self fucNewName:&name age:&age money:&money isRichGuy:&isRichGuy degress:°ress];
NSLog(@"name====%@",name);
}
-(void)fucNewName:(NSString **)newName
age:(NSInteger *)age
money:(NSString **)money
isRichGuy:(BOOL *)isRichGuy
degress:(CGFloat *)degress
{
*newName = @"fuck";
*age = 20;
*money = @"2000";
*isRichGuy = NO;
*degress = 0.66;
}
看出來了吧不脯,返回參數(shù)可以不用放容器里返回,完全可以直接修改函數(shù)外面的值,也不用擔心NSInteger這些變量不能直接放進容器里面的問題刻诊。
4防楷、空指針nil、野指針则涯、僵尸對象
1.空指針
值為nil复局,沒有指向。由于iOS中采用的是對調(diào)用者發(fā)消息粟判,如果消息的接受者為nil亿昏,對空指針發(fā)任何消息不起任何作用。
NSObject * object = nil;
NSLog(@"函數(shù)==%p", &object);//函數(shù)==0x7ffeeaba0ba8
NSLog(@"函數(shù)==%p", object);//函數(shù)==0x0
這里object存儲的是0x0,表示object是一個空指針,空指針也是指針档礁,也有四元素角钩。
2.野指針
不是nil指針,是指向"垃圾"內(nèi)存(不可用內(nèi)存)的指針呻澜。當所指向的對象被釋放或者收回递礼,但是對該指針沒有作任何的修改,以至于該指針仍舊指向已經(jīng)回收的內(nèi)存地址易迹,此情況下該指針便稱野指針
宰衙。如用assign修飾對象就出現(xiàn)導致野指針,因為對象創(chuàng)建出來沒有任何強指針指向它睹欲,所以創(chuàng)建完以后立即會被釋放了供炼,這時候指針指向的地方已經(jīng)不可用,所以就成了野指針窘疮。
3.僵尸對象
,在OC中袋哼,對象被釋放后所占用的內(nèi)存在沒有被復寫(重新分配給其他對象)前稱為僵尸對象
,這是野指針是可以訪問該內(nèi)存的闸衫,因為對象的數(shù)據(jù)還在涛贯,所以程序不會報錯。但是該內(nèi)存一旦重新分配給其他對象就會出現(xiàn)問題蔚出。
最后給出最開始題目的答案: NSArray的enumerateObjectsUsingBlock實現(xiàn)類似下面:
- (void) enumTestBlock:(void (^)(id obj, NSUInteger index, BOOL * stop))enumBlock {
BOOL stopNow = NO;
for (int index = 0; index < self.count; index++ ) {
if (!stopNow) {
enumBlock(self[index], index, &stopNow);
} else {
break;
}
}
}
一些自己的理解記錄下來弟翘,希望沒有不對的地方。
參考1:http://www.reibang.com/p/5b2c7bbc32d6
參考2:http://www.reibang.com/p/c58e089ba219
參考3:https://blog.csdn.net/wnnvv/article/details/81144219