總結一些iOS的底層面試題。鞏固一下iOS的相關基礎知識。ios
若有出入,還望各位大神指出。git
NSObject對象建立實例對象的時候系統分配了16個內存(經過malloc_size函數可得到)程序員
可是 NSObject只使用了8個字節 使用(class_getinstanceSize可得到)github
利用Runtime API 動態生成了一個新的類, 而且instance對象的isa指針指向這個生成的新子類。面試
當修改instance對象的屬性時,會調用Foundation的_NSSetXXXValueForKey函數算法
輕量級KVO框架:GitHub - facebook/KVOController數據庫
不能直接給Category添加成員變量,可是能夠間接添加。編程
///> DLPerson+Test.h
@interface DLPerson (Test)
///> 若是直接使用 @property 只會生成方法的聲名 不會生成成員變量和set、get方法的實現。
@property (nonatomic, assign) int weigjt;
@end
///> DLPerson+Test.m
#import "DLPerson+Test.h"
@implemention DLPerson (Test)
NSMutableDictionary weights_;
+ (void)load{
weights_ = [NSMutableDictionary alloc]init];
}
- (void)setWeight:(int)weight{
NSString *key = [NSString stringWithFormat:@"%p",self];
weights_[key] = @(weight);
}
- (int)weight{
NSString *key = [NSString stringWithFormat:@"%p",self];
return [weights_[key] intValue]
}
@end
複製代碼
使用runtime機制給分類添加屬性swift
#import<objc/runtime.h>
const void *DLNameKey = &DLNameKey
///> 添加關聯對象
void objc_setAssociatedObject(
id object, ///> 給哪個對象添加關聯對象
const void * key, ///> 指針(賦值取值的key) &DLNameKey
id value, ///> 關聯的值
objc_AssociationPolicy policy ///> 關聯策略 下方表格
)
eg : objc_setAssociatedObject(self,@selector(name),name,OBJC_ASSOCIATION_COPY_NONATOMIC);
///> 得到關聯對象
id objc_getAssociatedObject(
id object, ///> 哪個對象的關聯對象
const void * key ///> 指針(賦值取值的key)
)
eg:
objc_getAssociatedObject(self,@selector(name));
/// _cmd == @selector(name);
objc_getAssociatedObject(self,_cmd);
///> 移除全部的關聯對象
void objc_removeAssociatedObjects(
id object ///>
)
複製代碼
objc_AssociationPolicy(關聯策略) | 對應的修飾符 |
---|---|
OBJC_ASSOCIATION_ASSIGN | assign |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | strong, nonatomic |
OBJC_ASSOCIATION_COPY_NONATOMIC | copy, nonatomic |
OBJC_ASSOCIATION_RETAIN | strong, atomic |
OBJC_ASSOCIATION_COPY | copy, atomic |
int main(int argc, const char *argv[]){
@autoreleasepool{
int age = 10;
void (^block)(void) = ^{
NSLog(@" age is %d ",age);
};
age = 20;
block();
}
}
/* 輸出結果爲? 爲何? 輸出結果是: 10 若是沒有修飾符 默認是auto 爲了能訪問外部的變量 block有一個變量捕獲的機制 由於他是局部變量 而且沒有用static修飾 因此它被捕獲到block中是 一個值,外部再次改變時 block中的age不會改變。 */
複製代碼
變量類型 | 捕獲到Block內部 | 訪問方式 | |
---|---|---|---|
局部變量 | auto | ✅ | 值傳遞 |
局部變量 | static | ✅ | 指針傳遞 |
全局變量 | ❌ | 直接訪問 |
int main(int argc, const char *argv[]){
@autoreleasepool{
int age = 10;
static int height = 10;
void (^block)(void) = ^{
NSLog(@" age is %d, height is %d",age, height);
};
age = 20;
height = 20;
block();
}
}
/* 輸出結果爲? 爲何? age is 10, height is 20 局部變量用static 修飾以後 捕獲到block中的是 height的指針, 所以修改經過指針修改變量以後 外部的變量也被修改了 */
複製代碼
int age = 10;
static int height = 10;
int main(int argc, const char *argv[]){
@autoreleasepool{
void (^block)(void) = ^{
NSLog(@" age is %d, height is %d",age, height);
};
age = 20;
height = 20;
block();
}
}
/* 輸出結果爲? 爲何? age is 20, height is 20 由於 age 和 height是全局變量不須要捕獲直接就能夠修改 全局變量 對應該就能夠訪問, 局部變量 須要跨函數訪問,因此須要捕獲 所以修改經過指針修改變量以後 外部的變量也被修改了 */
複製代碼
int main(int argc, const char *argv[]){
@autoreleasepool{
void (^block)(void) = ^{
NSLog(@" self %p",self);
};
block();
}
}
/* self 會不會被捕獲? 由於函數默認會有兩個參數 void test(DLPerson *self, SEL _cmd) 因此self 也是一個局部變量 訪問@property(nonatmic, copy) NSString *name; 由於他是成員變量, 訪問的時候 用self.name 或者 self->_name 訪問 因此 block在內部會捕獲self。 */
複製代碼
ios內存分爲5發區域設計模式
block有三種類型,最終都繼承自NSBlock類型
block類型 | 環境 | 存放位置 |
---|---|---|
NSGlobalBlock | 沒有訪問auto變量 | 靜態區 |
NSStackBlock | 訪問了auto變量 | 棧 |
NSMallocBlock | __NSStackBlock__調用了copy | 堆 |
__NSStackBlock__調用了copy 代碼實例
void (^block)(void);
void (^block1)(void);
void test2(){
int age = 10;
block = ^{
NSLog("age is %d",age);
/* 由於 block訪問了 auto變量 因此目前block的類型爲NSStackBlock類型, 存放的位置在 棧 上 在 main訪問是 這個block已經被釋放了。 */
};
[block1 = ^{
NSLog("age is %d",age);
/* 由於 block訪問了 auto變量 而且 進行了copy操做 因此目前block的類型爲 NSMallocBlock 類型 存放的位置在 堆 上 在 main訪問是 這個block是能夠被訪問的。 */
} copy];
}
int main(int argc, const char *argv[]){
@autoreleasepool{
test2();
block(); /// 輸出的值爲 很大不是想要的值
block1();/// 輸出的值是10
}
}
複製代碼
每一種類型的block調用了copy以後結果以下所示
block的類型 | 副本源的配置存儲域 | 複製後的區域 |
---|---|---|
NSGlobalBlock | 程序的數據區域 | 什麼都不作 |
NSStackBlock | 棧 | 從棧複製到堆 |
NSMallocBlock | 堆 | 引用計數器+1 |
block做爲函數的返回值的時候
將block賦值給__strong指針時
block做爲 Cocoa API中方法名含有usingBlock的方法參數時
NSArray *arr = @[];
/// 遍歷數組中包含 usingBlock方法的參數
[arr enumerateObjectUsingBlock:^(id _Nonnullobj, NSUInteger idx, Bool _Nonnull stop){
}] ;
複製代碼
block做爲GCD屬性的建議寫法
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
disPatch_after(disPatch_time(IDSPATCH_TIME_NOW, (int64_t)(delayInSecounds *NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
複製代碼
MRC下block屬性建議寫法
ARC下block屬性建議寫法
isMemberOfClass源碼:
/// 返回的直接是 是不是當前的類, 當前類對象
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
/// 返回的直接是 是不是當前的類,
/// 當前元類對象
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
複製代碼
iskindOfClass源碼:
/// for循環查找 , 會根據當前類和 當前類的額父類去逐級查找 ,
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
/// for循環查找 , 會根據當前類和 當前類的額父類去逐級查找 ,
/// 當前元類對象
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
複製代碼
相關面試題:
// NSLog(@"%d", [[NSObject class] isKindOfClass:[NSObject class]]);
// NSLog(@"%d", [[NSObject class] isMemberOfClass:[NSObject class]]);
// NSLog(@"%d", [[MJPerson class] isKindOfClass:[MJPerson class]]);
// NSLog(@"%d", [[MJPerson class] isMemberOfClass:[MJPerson class]]);
/// 上面的寫法 與 下面的寫法 相同
// 這句代碼的方法調用者不論是哪一個類(只要是NSObject體系下的、繼承於NSObject),都返回YES
NSLog(@"%d", [NSObject isKindOfClass:[NSObject class]]); // 1
NSLog(@"%d", [NSObject isMemberOfClass:[NSObject class]]); // 0
NSLog(@"%d", [MJPerson isKindOfClass:[MJPerson class]]); // 0
NSLog(@"%d", [MJPerson isMemberOfClass:[MJPerson class]]); // 0
// 輸出的結果是什麼?
複製代碼
技術方案 | 簡介 | 語言 | 線程生命週期 | 使用頻率 |
---|---|---|---|---|
pthread | 1. 一套通用的多線程API 2. 適用於Unix/Linux/Windows等系統 3. 跨平臺、可移植 4. 使用難度大 |
C | 程序員管理 | 幾乎不用 |
NSThread | 1. 使用更加面向對象 2. 簡單易用,可直接操做線程對象 |
OC | 程序員管理 | 偶爾使用 |
GCD | 1. 旨在代替NSThread等線程技術 2. 充分利用設備的多核 |
C | 自動管理 | 常用 |
NSOperation | 1. 基於GCD(底層是GCD) 2. 比GCD多了一些簡單使用的功能 3. 使用更加面向對象 |
OC | 自動管理 | 常用 |
併發隊列 | 手動建立串行隊列 | 主隊列 | |
---|---|---|---|
同步 | 沒有開啓新線程 串行執行任務 |
沒有開啓新線程 串行執行任務 |
沒有開啓新線程 串行執行任務 |
異步 | 開啓新線程 並行執行任務 |
開啓新線程 串行執行任務 |
沒有開啓新線程 串行執行任務 |
- (void)interview01{
///> 會發生死鎖,
NSLog(@"任務1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"任務2");
});
NSLog(@"任務3");
/// dispatch_sync 須要立馬在當前線程 同步執行任務 當前在主線程中
/// 而主隊列須要等 主線程的東西執行完以後纔會執行。 因此形成了死鎖
}
- (void)interview02{
///> 不會發生死鎖
NSLog(@"任務1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"任務2");
});
NSLog(@"任務3");
// dispatch_sync 不須要須要立馬在當前線程 同步執行任務 因此等待主線程執行結束以後才執行的
}
- (void)interview03{
///> 會產生死鎖
NSLog(@"任務1");
dispatch_queue_t queue = dispatch_queue_create("muqueue", DISPATCH_QUEUE_SERIAL); /// 同步;
dispatch_async(queue, ^{
NSLog(@"任務2");
dispatch_sync(queue, ^{ //死鎖
NSLog(@"任務3");
});
NSLog(@"任務4");
});
NSLog(@"任務5");
}
- (void)interview04{
///> 不會產生死鎖
NSLog(@"任務1");
dispatch_queue_t queue = dispatch_queue_create("muqueue", DISPATCH_QUEUE_SERIAL); /// 串行;
// dispatch_queue_t queue2 = dispatch_queue_create("muqueue2", DISPATCH_QUEUE_CONCURRENT); /// 併發;
dispatch_queue_t queue2 = dispatch_queue_create("muqueue2", DISPATCH_QUEUE_SERIAL); /// 串行; 也不會
dispatch_async(queue, ^{
NSLog(@"任務2");
dispatch_sync(queue2, ^{ //死鎖
NSLog(@"任務3");
});
NSLog(@"任務4");
});
NSLog(@"任務5");
//不會產生死鎖 由於兩個任務不在同一個隊列之中, 因此不存在互相等待的問題。
}
- (void)interview05{
///> 不會產生死鎖
NSLog(@"任務1 thread:%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("muqueue2", DISPATCH_QUEUE_CONCURRENT); /// 併發;
dispatch_async(queue, ^{
NSLog(@"任務2 thread:%@",[NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"任務3 thread:%@",[NSThread currentThread]);
});
NSLog(@"任務4 thread:%@",[NSThread currentThread]);
});
NSLog(@"任務5 thread:%@",[NSThread currentThread]);
//不會產生死鎖 由於兩個任務不在同一個隊列之中, 因此不存在互相等待的問題。
}
複製代碼
- (void)test{
NSLog(@"2");
}
- (void)touchesBegan03{
// NSThread *thread = [[NSThread alloc]initWithBlock:^{
// NSLog(@"1");
//
// }];
// [thread start];
// [self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
// 運行後會崩潰 由於子線程 performSelector方法 沒有開啓runloop, 當執行test的時候這個線程已經沒有了。
NSThread *thread = [[NSThread alloc]initWithBlock:^{
NSLog(@"1");
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}];
[thread start];
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
/// r添加開啓runloop後 在線程中有runloop存在線程就不會死掉, 以後調用performSelect就沒有問題了
}
- (void)touchesBegan02{
/// 建立全局併發隊列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1");
//[self performSelector:@selector(test) withObject:nil];/// 打印結果 1 2 3 等價於[self test]
/// 這句代碼點進去發現是在Runloop中的方法
/// 本質就是向Runloop中添加了一個定時器。 子線程默認是沒有啓動 Runloop的
[self performSelector:@selector(test) withObject:nil afterDelay:.0]; /// 打印結果 1 3
NSLog(@"3");
/// 啓動runloop
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
});
}
- (void)touchesBegan01{
/// 建立全局併發隊列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1");
// [self performSelector:@selector(test) withObject:nil];/// 打印結果 1 2 3 等價於[self test]
/// 這句代碼點進去發現是在Runloop中的方法
// 本質就是向Runloop中添加了一個定時器。 子線程默認是沒有啓動 Runloop的
[self performSelector:@selector(test) withObject:nil afterDelay:.0]; /// 打印結果 1 3
NSLog(@"3");
});
}
複製代碼
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("muqueue", DISPATCH_QUEUE_CONCURRENT);// 併發隊列
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任務1 thread --- %@",[NSThread currentThread]);
}
});
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任務2 thread --- %@",[NSThread currentThread]);
}
});
///> 回到主線程執行 任務3
// dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// for (int i = 0; i < 5; i++) {
// NSLog(@"任務3 thread --- %@",[NSThread currentThread]);
// }
// });
///> 執行完任務一、2以後再執行任務三、4
dispatch_group_notify(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任務3 thread --- %@",[NSThread currentThread]);
}
});
dispatch_group_notify(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任務4 thread --- %@",[NSThread currentThread]);
}
});
}
複製代碼
光柵化 layer.shouldRasterize = YES;
遮罩,layer.mask =
圓角,同事設置layer.maskToBounds = YES、layer.cornerRadius大於0
imageView.layer.masksToBounds = YES;
[self.view addSubview:imageView];
複製代碼
UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
imageView.image = [UIImage imageNamed:@"1"];
//開始對imageView進行畫圖
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
//使用貝塞爾曲線畫出一個圓形圖
[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds cornerRadius:imageView.frame.size.width] addClip];
[imageView drawRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
//結束畫圖
UIGraphicsEndImageContext();
[self.view addSubview:imageView];
複製代碼
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
imageView.image = [UIImage imageNamed:@"1"];
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:imageView.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:imageView.bounds.size];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc]init];
//設置大小
maskLayer.frame = imageView.bounds;
//設置圖形樣子
maskLayer.path = maskPath.CGPath;
imageView.layer.mask = maskLayer;
[self.view addSubview:imageView];
}
複製代碼
陰影, layer.shadowXXX
平時所說的「卡頓」主要是由於在主線程執行了比較耗時的操做
能夠添加Observer到主線程RunLoop中,經過監聽RunLoop狀態切換的耗時,以達到監控卡頓的目的
編譯器優化
利用AppCode(AppCode_下載連接)檢測未使用的代碼:
編寫LLVM插件檢測出重複代碼、未被調用的代碼
生成LinkMap文件,能夠查看可執行文件的具體組成
設計模式(Design Pattern)
設計模式能夠分爲三大類