目錄:
- static作用
- 各種各樣的nullable
- if 和 switch的區(qū)別
- __auto_type
- interface和protocol
- null & kCFNull
- ObjC的BOOL為什么要用YES、NO而不建議用true、false
1. static作用
Effective OC里面也說過static,這里再復習一下~ 可參考:http://www.itdecent.cn/p/9c09989b6862
如果是static的局部變量,是會在app生命周期內(nèi)有效的,也就是app銷毀的時候才會銷毀,即使作用域是在花括號內(nèi)。
如果是全局變量呢?默認情況下,全局變量在整個程序中是可以被訪問的(即全局變量的作用域是整個項目文件)
static修飾全局變量,是為了讓這個變量的作用域局限在當前文件內(nèi)。防止兩個不同的文件里面定義了相同名字的一個全局變量那么就會報錯duplicate definition。
那么一個問題是,static修飾的屬性是在堆上還是棧上嘞?
全局區(qū)(靜態(tài)區(qū)) (static) 全局變量和靜態(tài)變量的存儲是放在一起的,初始化的全局變量和靜態(tài)變量存放在一塊區(qū)域,未初始化的全局變量和靜態(tài)變量在相鄰的另一塊區(qū)域,程序結束后有系統(tǒng)釋放。
=> 關于內(nèi)存分配可以參考:http://www.itdecent.cn/p/f3c1b920e8eb另一個point是extern是干啥得嘞?
即使你不import一個定義了全局變量的文件,仍舊可以用extern得到該變量,也就是說extern可以脫離import訪問所有全局變量(除開用static修飾的作用域僅在當前文件內(nèi)的全局變量)app生命周期內(nèi)的變量聲明
如果有app整個生命周期只需要執(zhí)行一次的事情,我一般都會想到dispatch_once,但如果是一個特定次數(shù)嘞?就需要用全局變量計次啦,這個時候就可以用static來做全局變量,加到去干活兒的類里面就可以了~
2. 各種各樣的nullable
由于Swift里面的變量都會指定是不是空,所以Objective-C為了和Swift兼容,每個屬性或每個方法都去指定nonnull和nullable。
蘋果為了減輕我們的工作量,定義了NS_ASSUME_NONNULL_BEGIN和NS_ASSUME_NONNULL_END兩個宏。在這兩個宏之間的所有簡單指針對象都被假定為nonnull。
可參考:http://www.itdecent.cn/p/d001550fadd4
那么修飾詞有哪些呢?
nullable、nonnull、null_resettable、_Null_unspecified以及首字母大寫的那種
偷懶直接借過來啦:
- nullable,表示屬性可以為空
修飾屬性的時候用nullable,修飾參數(shù)的時候在星號后面加_Nullable或者__nullable
@property (nonatomic, copy, nullable) NSString *name;
@property (nonatomic, copy) NSString *_Nullable name;
@property (nonatomic, copy) NSString *__nullable name;
- nonnull,表示屬性不能為空
同上類似
@property (nonatomic, copy, nonnull) NSString *name;
@property (nonatomic, copy) NSString *_Nonnull name;
@property (nonatomic, copy) NSString *__nonnull name;
- null_resettable,表示可以重新設置空,set方法可以為空,get不能為空
@property (nonatomic, copy, null_resettable) NSString *name;
必須重寫get或set方法,做判空處理
- (NSString*)name{
if (_name == nil){
_name = @"不能為空";
}
return _name;
}
- _Null_unspecified,不確定是否為空
@property (nonatomic, strong) NSString * _Null_unspecified name;
3. if 和 switch的區(qū)別
if 和 switch 相比大家肯定會prefer switch,他倆的確也是有性能差異的,但為啥switch會比if好呢?
可參考:https://blog.csdn.net/hixiaogui/article/details/79785920
https://blog.csdn.net/xi_niuniu/article/details/45101093
if:
比較中規(guī)中矩,會一條一條的向下執(zhí)行,直到條件滿足,執(zhí)行完函數(shù)體就結束switch:分倆種情況
1.當case語句數(shù)量<4 時,效率上和if語句基本一樣
2.當case語句數(shù)量>=4時,case會有一個跳轉表,存儲著每個case和對應的跳轉體的地址,效率明顯比if效率高。
switch 指令是一個有索引的跳轉,而if ... else 是無索引的跳轉。if...else 是 O(N)級別的,switch ... case 是 O(1)級別的。
我理解其實就類似一個數(shù)組,然后根據(jù)switch的值去找case,所以就很target定位到case啦~ 具體原理歡迎還是看refer吧,我感覺我的匯編水準理解不到那個層次QAQ

舉個例子~
- (void)testSwitchIf {
NSInteger i = 9;
NSTimeInterval start = [[NSDate date] timeIntervalSince1970];
if (i == 1) {
NSLog(@"i == 1");
} else if (i == 2) {
NSLog(@"i == 2");
} else if (i == 3) {
NSLog(@"i == 3");
} else if (i == 4) {
NSLog(@"i == 4");
} else if (i == 5) {
NSLog(@"i == 5");
} else if (i == 6) {
NSLog(@"i == 6");
} else if (i == 7) {
NSLog(@"i == 7");
} else if (i == 8) {
NSLog(@"i == 8");
} else if (i == 9) {
NSLog(@"i == 9");
} else if (i == 10) {
NSLog(@"i == 10");
}
NSTimeInterval timeConsume = [[NSDate date] timeIntervalSince1970] - start;
NSLog(@"if time consume:%f", timeConsume);
start = [[NSDate date] timeIntervalSince1970];
switch (i) {
case 1:
NSLog(@"i == 1");
break;
case 2:
NSLog(@"i == 2");
break;
case 3:
NSLog(@"i == 3");
break;
case 4:
NSLog(@"i == 4");
break;
case 5:
NSLog(@"i == 5");
break;
case 6:
NSLog(@"i == 6");
break;
case 7:
NSLog(@"i == 7");
break;
case 8:
NSLog(@"i == 8");
break;
case 9:
NSLog(@"i == 9");
break;
case 10:
NSLog(@"i == 10");
break;
default:
break;
}
timeConsume = [[NSDate date] timeIntervalSince1970] - start;
NSLog(@"switch time consume:%f", timeConsume);
}
===
輸出:
2020-06-14 20:36:21.092568+0800 Example1[31090:6201959] if time consume:0.000059
2020-06-14 20:36:21.092617+0800 Example1[31090:6201959] switch time consume:0.000020
switch會比if快了1/3的樣子~ 所以日常一定記得能switch就別if哦~
4. __auto_type類型推導
參考:http://www.itdecent.cn/p/551908b48bb0
其實就是類似swift里面的var,醬紫你就可以直接定義對象了,不用聲明它的類型,會自動根據(jù)值來獲取類型~ 我們的小哥哥反正其實沒啥用,稍微簡寫了一點兒主要是顯得高大上...
__auto_type string = @"test";
__auto_type subString = [string substringFromIndex:1];
NSLog(@"%@",subString);
//正常寫法
void(^testBlock)(NSString *,NSNumber *) = ^(NSString *string,NSNumber *number){
NSLog(@"testBlock %@ - %@",string,number);
};
//類型推導
__auto_type test = ^(NSString *string,NSNumber *number){
NSLog(@"__auto_type %@ - %@",string,number);
};
testBlock(@"1",@(2));
test(@"3",@(4));
- 還有一個花式寫法可以避免view名字過長不斷重復寫這個view的名字~ 也是裝逼利器~
UIView *seperateView = ({
UIView *view = [[UIView alloc] init];
view.backgroundColor = mRGBColor(233, 233, 233);
[contentView addSubview:view];
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.width.equalTo(contentView);
make.height.equalTo(@0.5);
make.top.equalTo(contentView).offset(47.5);
}];
view;
});
5. interface和protocol
OC中@interface+@implements共同構成一個類,不能只有一個哦,必須成對存在;而@protocol作為一個接口,可以沒有實現(xiàn)。
這個是我忘記寫implements報錯遇到的一個問題捂臉0.0

6. nil & kCFNull
參考:http://www.itdecent.cn/p/3aaefb3bcf73
我看代碼解析后端返回的時候用的kCFNull判空,小哥哥說其實就是后端的null解析以后就變成kCFNull了,于是我查了一下~
nil: define the id of a null instance, 指向一個(實例)對象的空指針
NSString *str = nil;
NSDate *date = nil;Nil:defines the id of a null class, 指向一個類的空指針
Class class = Nil;NULL:定義其他類型(基本類型,C類型)的空指針
char *p = NILL;NSNull:數(shù)組中元素的占位符, 數(shù)據(jù)中的元素不能為nil(可以為空,也就是NSNull)
原因:nil 是組數(shù)的結束標識
如果使用nil,在n個數(shù)組中的第k個,那個數(shù)組的長度就只有 k 個元素。kCFNull: NSNull 的單例
NSNull *null1 = (id)kCFNull;
NSNull *null2 = [NSNull null];
NSNull *null3 = [NSNull null];
地址:
null1 NSNull * 0x1ffcdfe28 0x00000001ffcdfe28
null2 NSNull * 0x1ffcdfe28 0x00000001ffcdfe28
null3 NSNull * 0x1ffcdfe28 0x00000001ffcdfe28
但是其實你如果想BOOL i = nil;也是OK的,寫代碼的時候不會有很大的差異。但是NSNull作為占位空對象和nil還是有區(qū)別的:
nil: 作為對象的空指針和數(shù)組的結束標志
NSNull: 作為數(shù)組中的空值占位符
7. ObjC的BOOL為什么要用YES、NO而不建議用true、false
可以參考:https://blog.csdn.net/weixin_34292287/article/details/87975252
這個問題吧是凌哥哥問我的,但是吧其實OC就是用YES和NO啊,其他就都是true false,就很理所當然自己也沒覺得有啥疑問,所以凌哥問了以后我就查了一下~
首先看下BOOL的定義:
#if TARGET_OS_OSX || (TARGET_OS_IOS && !__LP64__ && !__ARM_ARCH_7K)
# define OBJC_BOOL_IS_BOOL 0
#else
# define OBJC_BOOL_IS_BOOL 1
#endif
#if OBJC_BOOL_IS_BOOL
typedef bool BOOL;
#else
# define OBJC_BOOL_IS_CHAR 1
typedef signed char BOOL;
#endif
可以近似的理解為在 64-bit 設備上 BOOL 實際是 bool 類型,在 32-bit 設備上 BOOL 的實際類型是 signed char
那么 YES / NO 又分別是什么值呢?我們看一下具體的定義:
#if __has_feature(objc_bool)#define YES __objc_yes#define NO __objc_no#else#define YES ((BOOL)1)#define NO ((BOOL)0)#endif復制代碼
這里要先看一下 __objc_yes 和 __objc_no 是什么值,我們在 LLVM 的文檔中可以得到答案:
The compiler implicitly converts __objc_yes and __objc_no to (BOOL)1 and (BOOL)0\. The keywords are used to disambiguate BOOL and integer literals.復制代碼
__objc_yes 和 __objc_no 其實就是 (BOOL)1 和 (BOOL)0,這么寫的原因就是為了消除 BOOL 和整型數(shù)的歧義而已。
(BOOL)1 和 (BOOL)0 這個大家應該也都能很容易理解了,其實就是把 1 和 0 強轉成了 BOOL 對應的實際類型。
所以綜上所述為了類型的正確對應,在給 BOOL 類型設值時要用
YES/NO,其實就是為了類型對應以及消除 BOOL 和整型數(shù)的歧義。
true / false 是什么?
#define bool _Bool
#define true 1
#define false 0
- 那么為什么要對 BOOL 用 YES / NO 而不是 true / false?
可以看到 ObjC 是自己定義了 BOOL 的類型,然后定義了對應要使用的值 YES / NO,理所當然的第一個原因是我們要按照標準來。
既然 ObjC 的 BOOL 使用的不是標準 C 的定義,那么以后這個定義可能還會修改。雖然說概率很低,但是畢竟從上面的代碼看就經(jīng)歷了 signed char 到 bool 的一次修改不是么?為了避免這種風險,建議還是要使用 YES / NO。
在某些情況下,類型不匹配會導致 warning,而 YES / NO 是帶類型的,可以保證類型正確,所以建議要用 YES / NO。
- 注意不要用
==YES這種
在32位的機子上,BOOL是signed char類型,所以會有下面的問題:
BOOL a = 2;
if (a) {
NSLog(@"a is YES");
} else {
NSLog(@"a is NO");
}
if (a == YES) {
NSLog(@"a == YES");
} else {
NSLog(@"a != YES");
}
32位機子輸出:
a is YES
a != YES
因為其實翻譯過來是醬紫的:
signed char a = 2;
if (a == (signed char)1) {
NSLog(@"a == YES");
} else {
NSLog(@"a != YES");
}
- 這就對應了另外一個問題了,在網(wǎng)絡請求的時候,我們經(jīng)常要傳給后端一個bool參數(shù),例如xxx=true。這個時候傳入
{@"xxx" : @"true"}和{@"xxx" : @(YES)}的區(qū)別是啥呢?
如果你用{@"xxx" : @(YES)}那么翻譯為param就變?yōu)榱?code>xxx=1因為YES其實就是1,但{@"xxx" : @"true"}會翻譯為xxx=true