目录:
- static作用
- 各种各样的nullable
- if 和 switch的区别
- __auto_type
- interface和protocol
- null & kCFNull
- ObjC的BOOL为什么要用YES、NO而不建议用true、false
1. static作用
Effective OC里面也说过static,这里再复习一下~ 可参考:https://www.jianshu.com/p/9c09989b6862
如果是static的局部变量,是会在app生命周期内有效的,也就是app销毁的时候才会销毁,即使作用域是在花括号内。
如果是全局变量呢?默认情况下,全局变量在整个程序中是可以被访问的(即全局变量的作用域是整个项目文件)
static修饰全局变量,是为了让这个变量的作用域局限在当前文件内。防止两个不同的文件里面定义了相同名字的一个全局变量那么就会报错duplicate definition
。
那么一个问题是,static修饰的属性是在堆上还是栈上嘞?
全局区(静态区) (static) 全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量存放在一块区域,未初始化的全局变量和静态变量在相邻的另一块区域,程序结束后有系统释放。
=> 关于内存分配可以参考:https://www.jianshu.com/p/f3c1b920e8eb另一个point是extern是干啥得嘞?
即使你不import一个定义了全局变量的文件,仍旧可以用extern得到该变量,也就是说extern可以脱离import访问所有全局变量(除开用static修饰的作用域仅在当前文件内的全局变量)app生命周期内的变量声明
如果有app整个生命周期只需要执行一次的事情,我一般都会想到dispatch_once,但如果是一个特定次数嘞?就需要用全局变量计次啦,这个时候就可以用static
来做全局变量,加到去干活儿的类里面就可以了~
2. 各种各样的nullable
由于Swift里面的变量都会指定是不是空,所以Objective-C为了和Swift兼容,每个属性或每个方法都去指定nonnull和nullable。
苹果为了减轻我们的工作量,定义了NS_ASSUME_NONNULL_BEGIN和NS_ASSUME_NONNULL_END两个宏。在这两个宏之间的所有简单指针对象都被假定为nonnull。
可参考:https://www.jianshu.com/p/d001550fadd4
那么修饰词有哪些呢?
nullable、nonnull、null_resettable、_Null_unspecified以及首字母大写的那种
偷懒直接借过来啦:
- nullable,表示属性可以为空
修饰属性的时候用nullable
,修饰参数的时候在星号后面加_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的区别
if 和 switch 相比大家肯定会prefer switch,他俩的确也是有性能差异的,但为啥switch会比if好呢?
可参考:https://blog.csdn.net/hixiaogui/article/details/79785920
https://blog.csdn.net/xi_niuniu/article/details/45101093
if:
比较中规中矩,会一条一条的向下执行,直到条件满足,执行完函数体就结束switch:分俩种情况
1.当case语句数量<4 时,效率上和if语句基本一样
2.当case语句数量>=4时,case会有一个跳转表,存储着每个case和对应的跳转体的地址,效率明显比if效率高。
switch 指令是一个有索引的跳转,而if ... else 是无索引的跳转。if...else 是 O(N)级别的,switch ... case 是 O(1)级别的。
我理解其实就类似一个数组,然后根据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类型推导
参考:https://www.jianshu.com/p/551908b48bb0
其实就是类似swift里面的var,酱紫你就可以直接定义对象了,不用声明它的类型,会自动根据值来获取类型~ 我们的小哥哥反正其实没啥用,稍微简写了一点儿主要是显得高大上...
__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作为一个接口,可以没有实现。
这个是我忘记写implements报错遇到的一个问题捂脸0.0
6. nil & kCFNull
参考:https://www.jianshu.com/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:数组中元素的占位符, 数据中的元素不能为nil(可以为空,也就是NSNull)
原因:nil 是组数的结束标识
如果使用nil,在n个数组中的第k个,那个数组的长度就只有 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还是有区别的:
nil: 作为对象的空指针和数组的结束标志
NSNull: 作为数组中的空值占位符
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 和整型数的歧义而已。
(BOOL)1
和 (BOOL)0
这个大家应该也都能很容易理解了,其实就是把 1 和 0 强转成了 BOOL 对应的实际类型。
所以综上所述为了类型的正确对应,在给 BOOL 类型设值时要用
YES
/NO
,其实就是为了类型对应以及消除 BOOL 和整型数的歧义。
true / false 是什么?
#define bool _Bool
#define true 1
#define false 0
- 那么为什么要对 BOOL 用 YES / NO 而不是 true / false?
可以看到 ObjC 是自己定义了 BOOL 的类型,然后定义了对应要使用的值 YES / NO,理所当然的第一个原因是我们要按照标准来。
既然 ObjC 的 BOOL 使用的不是标准 C 的定义,那么以后这个定义可能还会修改。虽然说概率很低,但是毕竟从上面的代码看就经历了 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");
}
- 这就对应了另外一个问题了,在网络请求的时候,我们经常要传给后端一个bool参数,例如xxx=true。这个时候传入
{@"xxx" : @"true"}
和{@"xxx" : @(YES)}
的区别是啥呢?
如果你用{@"xxx" : @(YES)}
那么翻译为param就变为了xxx=1
因为YES其实就是1,但{@"xxx" : @"true"}
会翻译为xxx=true