iOS面試題之block
1、Block是OC中非常重要的一種技術手段 Block是C語音中的一個數據型別,一個函式體(匿名函式)、在需要時呼叫、可能有引數、可能有返回值。 2、從c函式來定義Block C函式寫法: int add(int num1, int num2) OC函式寫法:-(void)show:(int num1) 由C到Block的轉變:void(^myBlock)() 3、Block的基本使用 建立一個命令列專案,選擇OS X->Application->Command Line Tool 寫程式碼之前牢記三句話 1、Block是C語言的 2、Block是一個數據型別 3、Block是一個提前準備好的程式碼,在需要的時候執行
(1、最簡單的Block
//Block是一個提前好的程式碼,所以在賦值的時候要賦一段程式碼(定義時,把Block當成一個數據型別)
void (^myBlock)() = ^{
NSLog(@"hello");
};
//執行,執行時把Block當成函式
myBlock();
至此,最簡單的Block就定義好了。它的兩個特點是:
1、型別比函式多了一個‘^’符號。
2、設定數值時,有一個‘^’符號,內容是{}括起來的一段程式碼。
(2、定義帶引數的Block
格式: ‘void (^Block名稱)(引數列表) = ^(引數列表){//程式碼實現}’
在程式碼中,如果要使用到引數,就必須要帶上變數名,所以定義x,y。在本程式碼中,把‘^’號後面的小括號當成一個引數。
void (^sumBlock)(int,int) = ^(int x,int y){
NSLog(@"%d",x+y);
}
//執行
sumBlock(10,20);
(3、定義帶返回值的Block
格式:‘返回值型別(
Block名稱)(引數列表)=
返回型別(引數列表){//程式碼實現}’int (
sumBlock2)(int,int)=
int(int a,int b){return a+b;
}
//執行
NSLog(@"%d",sumBlock2(4,3));
此處恰好說明,Block是一段提前準備好的程式碼,當呼叫它時,它將返回值返回。
block的速記符號:在程式碼中敲"inlineBlock",xCode會返回已經定義好的Block格式。
4、Block的應用場景
如果有這樣的程式碼,每個函式都有很多重複的部分,這個時候我們通常會想到要重構程式碼。也就是說把相同的程式碼抽取出來,放在一個地方維護,如果需求變化就只要調整一個地方。
void day1(){
NSLog(@"起床");
NSLog(@"上班");
NSLog(@"入職");
NSLog(@"要賬號");
NSLog(@"看程式碼");
NSLog(@"下班");
NSLog(@"睡覺");
}
void day2(){
NSLog(@"起床");
NSLog(@"上班");
NSLog(@"需求分析");
NSLog(@"熟悉公司環境");
NSLog(@"下班");
NSLog(@"睡覺");
}
void day3(){
NSLog(@"起床");
NSLog(@"上班");
NSLog(@"寫程式碼");
NSLog(@"開會");
NSLog(@"測試");
NSLog(@"下班");
NSLog(@"睡覺");
}
void day4(){
NSLog(@"寫程式碼");
NSLog(@"測試");
}
重構的步驟:
1、新建一個方法
2、將重複的程式碼複製到新方法中
3、根據需求,調整引數。
重構的內容:建立一個新的方法
void myWork(){
//這幾句每個方法都一樣的
NSLog(@"起床");
NSLog(@"出門");
NSLog(@"坐車");
//中間是每天的工作內容
//…… 有可能有幾條
NSLog(@"下班");
NSLog(@"回家");
NSLog(@"睡覺");
}
在上面程式碼中,有很多重複的事情要做。但是每天的工作內容是不一樣的,也就是說要傳入不同的引數。於是問題來了,該怎樣去確定重構的引數呢?
其實我們只要將程式碼放入其中,讓它執行即可。而Block正是一段預先準備好的程式碼,在需要的時候執行。
在每天的工作內容中,呼叫一個定義好的block,每次呼叫都會執行block的內容,該問題就能迎刃而解。現在來寫重構的程式碼。
void myWork(void(^dayWork)()){
NSLog(@"起床");
NSLog(@"出門");
NSLog(@"坐車")
//這裡呼叫引數
dayWork();
NSLog(@"下班");
NSLog(@"回家");
NSLog(@"睡覺");
}
此時,在其他普通的方法中,就可以省去那些多餘的程式碼,只要把不一樣的程式碼傳入到block即可。
void day1(){
myWork(^{
NSLog(@"入職");
NSLog(@"要賬號");
NSLog(@"看程式碼");
});
}
在主函式中呼叫函式day1()
int main(int argc,const cahr * argv[]){
@autoreleasepool {
day1();
}
return 0;
}
自此已經可以將重複的程式碼省去了。
其它方法也以此類推,寫出每天工作中不同的操作。
void day2(){
myWork(^{
NSLog(@"需求分析");
NSLog(@"熟悉公司環境")
});
}
void day3(){
myWork(^{
NSLog(@"寫程式碼");
NSLog(@"開會")
});
}
void day4(){
myWork(^{
NSLog(@"寫程式碼");
NSLog(@"開會")
NSLog(@"測試")
});
}
呼叫
int main(int argc,const cahr * argv[]){
@autoreleasepool {
day1();
day2();
day3();
day4();
}
return 0;
}
分割線 幾何形
於是,問題又來了。
day1()-day5()的呼叫實在太過死板,是否能實現一次呼叫呢?
思路:這個時候,我們應該建立一個新的方法,設定一個day的引數,根據day來決定具體執行哪個塊的程式碼。
void dailyWork(int day){
//定義一個block變數,沒有設定程式碼
void(^work)();
//根據day來決定呼叫哪些程式碼
switch(day){
case 1:
work = ^{
NSLog(@"入職");
NSLog(@"要賬號");
NSLog(@"看程式碼");
};
break;
case 2:
work = ^{
NSLog(@"需求分析");
NSLog(@"熟悉公司環境")
};
break;
case 3:
work = ^{
NSLog(@"寫程式碼");
NSLog(@"開會")
};
break;
case 4:
work = ^{
NSLog(@"寫程式碼");
NSLog(@"開會")
NSLog(@"測試")
};
break;
default:
//不是day1-4時呼叫
work = ^{
NSLog(@"週末不加班");
}
break;
}
//*這裡很重要
//上面已經做完賦值操作了,此時,呼叫block
//此時正是體現了,block可以當做引數傳遞這一原理。
myWork(work);
}
呼叫
int main(int argc,const cahr * argv[]){
@autoreleasepool {
for(int i=0;i<7 xss=removed xss=removed xss=removed>
@interface EditViewController : UIViewController
@property(nonatomic,copy)void (^completion)(NSString *text);
@end
在.m檔案中執行塊程式碼
在這段程式碼中,需要將使用者輸入的文字屬性傳到上個控制器中。
//.m檔案
@interface EditViewController ()
@property (weak, nonatomic) IBOutlet UITextField *nameText;
@end
@implementation EditViewController
-
(IBAction)save:(id)sender {
//block預先準備好的程式碼
self.completion(self.nameText.text);
[self.navigationController popViewControllerAnimated:YES];
}
@end
如此,被執行的這邊已經完成,
第一個頁面的準備工作
傳值工作一般在prepareForSegue方法裡面操作。在這個方法中,先獲取到下一個頁面的控制器。在它的block的值傳給第一個介面的標籤儲存。
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
EditViewController *editVC = segue.destinationViewController;
editVC.completion = ^(NSString *str){
self.nameLabel.text = str;
};
}
總結
呼叫方:準備塊程式碼。類似於代理方式協議方法的實現,不同的是塊程式碼都在一起並沒有單獨的實現一個方法。
被呼叫方: 執行塊程式碼。前提是有要執行的程式碼,也就是在.h檔案中定義一個塊程式碼的屬性,接著在需要的時候執行即可。
結果
6、Block常見面試題
1、下面這段程式碼輸出為多少?
void demoBlock() {
int x = 10;//外部變數x
void(^myBlock)() = ^ {
NSLog(@”%d”,x);
};
x = 20;//在外部修改x的值
myBlock();
}
答案:輸出為10。在定義block的時候,如果引用了外部變數,會對外部變數進行拷貝。記錄住在block時變數的數值。如果之後在外部修改變數的值也不會影響block內部的數值變化。
2、如果在block裡面修改外部變數的值,會報錯嗎?
void demoBlock() {
int x = 10;//外部變數x
void(^myBlock)() = ^ {
x = 80
NSLog(@”%d”,x);
};
x = 20;//在外部修改x的值
myBlock();
}
答案:在預設情況下,不允許block內部修改外部變數的數值。因為拷貝之後它與原數值指向了不同的地址。若是能改變則會破壞程式碼的可讀性。
3、需求千變萬化。若我非要將x的值改成80又當如何?為什麼?
void demoBlock() {
__block int x = 10
int x = 10;//外部變數x
void(^myBlock)() = ^ {
};
myBlock();
NSLog(@”%d”,x);
};
答案:如果要在block中,修改外部變數的值,需要用__block修飾符號。使用了__block,說明函式不關心具體數值的具體變化。
在block中,如果引用了外部使用的__block變數,block定義之後,外部變數的指標地址同樣會變成堆區的地址。之所以可以修改是因為它們的指標指向的地址是相同的。
4、下面是一段能正確執行的程式碼。為啥定義成mutableString能在block內部對外部變數進行修改了?
void demoBlock() {
NSMutableString *strM = [NSMutableString stringWithString:@”張三”];
void(^myBlock)() = ^{
[strM setString:@”lisa”];
};
myBlock();
NSLog(@”%@”,strM);
}
答案:在block中引用外部變數要拷貝一份到堆中。
這個時候拷貝到堆中的地址與在棧中的地址是一樣的。因此,它指向zhangsan
在setString的時候,將指標指向的地址的值進行修改。
但是如果用stringWithString方法就會報錯,因為這相當於又開闢了一個新的空間。在堆中將strM的地址指向了新的空間。
喜歡的話可以點個贊+1:或關注多多支援哦 小編會經常給小夥伴們更新關於IOS當下熱點。
另外小編給大家推薦一個iOS技術交流群:458839238!群內提供資料結構與演算法、底層進階、swift、逆向、整合面試題等免費資料
附上一份收集的各大廠面試題(附答案) ! 群檔案直接獲取
各大廠面試題
文章來源網路 如有侵權請聯絡小編刪除