和JS交互碰到”‘“会打断字符串
被打断的字符串,会造成调用JS失败,这个时候要进行转义,添加一个转义符还不行,必须两个.
string = [string stringByReplacingOccurrencesOfString:@"'" withString:@"\\'"];
为什么需要两个转义符
第一个转义符用来转义第二个转义符,第二个转义符用来转义单引号,第一个转义符交给WebView用把被转义的”'“穿送给JS文件,作为字符串的一部分,然后在JS使用第二个转义符转义”‘“;
一句话概括: 有且仅有一个实例化对象的类,可以全局访问
单例的原理:
因为已经是Xcode7.2了,所以仅仅讨论ARC模式下,以下是各种Pods库常用的单例创建模式.
//.h
@interface ExampleSingleton : NSObject
+ (instancetype)shareInstance;
@end
//.m
@implementation ExampleSingleton
+ (instancetype)shareInstance {
static ExampleSingleton *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[ExampleSingleton alloc] init];
});
//NSLog(@"Access ExampleSingleton ShareInstance %p",sharedInstance);
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//Initial Data
});
}
return self;
}
@end
是GCD里一种计数器,本身是个long类型,每次执行一次就自动减1,直到数值小于0,不再执行.dispatch_once_t初始化的值为0,执行一次后为-1,下次再dispatch_once时由于小于0就不再执行.
GCD计数在读取通讯录里也用到了dispatch_semaphore_t,可以自定义执行几次
/*!
* @typedef dispatch_once_t
*
* @abstract
* A predicate for use with dispatch_once(). It must be initialized to zero.
* Note: static and global variables default to zero.
*/
typedef long dispatch_once_t;
Tips: 值得注意的是**dispatch_once(&onceToken, ^{});**采用的是传址形式,因为long为C类型的数据,详见我的C类型变量传值和传址的文章.
由于通常单例只能被创建一份,并且伴随着Application的生命周期可以全局访问,所以好多教程中都说单例不可以被释放.其实这个观点是错误的,单例不可被释放只是保证了他的安全性.
如果我有一个模块,需要一个资源池,但是我不保证模块什么时候被启动,设置一个伴随着Application的单例感觉会浪费内存,可不可以实现随着模块启动创建资源池,模块关闭停止资源池.以下是我自己可以随时启动和关闭的单例.
@interface ExampleSingleton : NSObject
+ (instancetype)shareInstance;
+ (void)haltSharedInstance;
@end
static ExampleSingleton *_sharedInstance = nil;
static dispatch_once_t _onceToken;
@implementation ExampleSingleton
+ (instancetype)shareInstance {
dispatch_once(&onceToken, ^{
_sharedInstance = [[ExampleSingleton alloc] init];
if(_sharedInstance) {
//Initial Data
}
NSLog(@"ExampleSingleton ShareInstance Did Create %p",sharedInstance);
});
//NSLog(@"Access ExampleSingleton %p",sharedInstance);
return _sharedInstance;
}
+ (void)haltSharedInstance {
if (_sharedInstance) {
_sharedInstance = nil;
_onceToken = 0;
}
}
- (instancetype)init {
self = [super init];
return self;
}
- (void)dealloc {
NSLog(@"ExampleSingleton SharedInstance Did Halted ");
}
关闭单例的原理是把静态的全局指针**_sharedInstance置为nil,从而使内存地址的retainCount为0,让ARC自动释放掉内存空间,并且把静态指针_onceToken**重新置为0,让下次执行shareInstance时可以再次初始化.
开发过程中又遇到一个需求从手机读取通讯录并且把姓名转为小写拼音进行排序,由于5C以前的机型转换小写拼音特别卡,所以想使用一个资源池,不同的功能都可以来访问,读取转换的结果,但是如果我长期不来访问,感觉这个单例占着内存不释放很不爽,而且万一用户在程序运行期间更新了通讯录,不知道何时更新资源池中的数据
为了这个需求,于是出现了以下这个作死的单例,功能如下
最终代码如下
@interface ExampleSingleton : NSObject
+ (instancetype)shareInstance;
+ (void)haltSharedInstance;
+ (void)resetTimer;
@end
static ExampleSingleton *_sharedInstance = nil;
static dispatch_once_t _onceToken;
static NSTimer *_timer = nil;
@implementation ExampleSingleton
+ (instancetype)shareInstance {
dispatch_once(&_onceToken, ^{
_sharedInstance = [[ExampleSingleton alloc] init];
if(_sharedInstance) {
//Initial Data
}
NSLog(@"ExampleSingleton ShareInstance Did Create %p",_sharedInstance);
});
NSLog(@"Access ExampleSingleton %p",_sharedInstance);
[self resetTimer];
return _sharedInstance;
}
+ (void)haltSharedInstance {
NSLog(@"SharedInstance Will Halted");
if (_sharedInstance) {
_sharedInstance = nil;
_onceToken = 0;
}
}
+ (void)resetTimer {
if (_timer.isValid) {
[_timer invalidate];
NSLog(@"SharedInstance Reset Timer");
}
_timer= [NSTimer scheduledTimerWithTimeInterval:600 target:self selector:@selector(haltSharedInstance) userInfo:nil repeats:NO];
}
- (void)dealloc {
NSLog(@"SharedInstance Did Halted ");
}
- (instancetype)init {
self = [super init];
if (self) { }
return self;
}
测试过程中十分钟改为10秒
+ (instancetype)shareInstance {
dispatch_once(&_onceToken, ^{
_sharedInstance = [[ExampleSingleton alloc] init];
});
_timer= [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(timeEndHaltSharedInstance) userInfo:nil repeats:NO];
return _sharedInstance;
}
- (void)timeEndHaltSharedInstance {
NSLog(@"SharedInstance Will Halted By Time ");
[[self class] haltSharedInstance];
}
第一版代码中,直接让_timer在shareInstance初始化,每次接入都重新初始化一次,这样上一次内存地址的**_timer**会被释放掉,然后执行halt函数.发现会Crash.原因是timeEndHaltSharedInstance是成员方法,类方法中的self是[self Class]类名,成员方法传给类名所以Crash.
+ (instancetype)shareInstance {
dispatch_once(&_onceToken, ^{
_sharedInstance = [[ExampleSingleton alloc] init];
});
_timer= [NSTimer scheduledTimerWithTimeInterval:10 target:_sharedInstance selector:@selector(timeEndHaltSharedInstance) userInfo:nil repeats:NO];
return _sharedInstance;
}
把执行地址改变之后,用_sharedInstance代替self,可以把成员方法发送给成员.但是产生了一个问题,由于存在成员方法,每次创建的timer和_sharedInstance会互相retain,所以接入了多少次就需要等多少次才能最后释放.日志如下
2016-01-12 16:46:32.276 Learn[31993:6644441] Access ShareInstance 0x7ffefac07870
2016-01-12 16:46:34.644 Learn[31993:6644441] Access ShareInstance 0x7ffefac07870
2016-01-12 16:46:34.644 Learn[31993:6644441] Reset Timer
2016-01-12 16:46:36.154 Learn[31993:6644441] Access ShareInstance 0x7ffefac07870
2016-01-12 16:46:36.155 Learn[31993:6644441] Reset Timer
2016-01-12 16:46:42.279 Learn[31993:6644441] SharedInstance Did Halted By Time
2016-01-12 16:46:44.645 Learn[31993:6644441] SharedInstance Did Halted By Time
2016-01-12 16:46:46.156 Learn[31993:6644441] SharedInstance Did Halted By Time
2016-01-12 16:46:46.156 Learn[31993:6644441] SharedInstance Did Halted
虽然最后总时间还是10秒,但是由于接入频率过高的时候,可能造成内存溢出,因为不能被回收的内存太多
+ (instancetype)shareInstance {
dispatch_once(&onceToken, ^{
_sharedInstance = [[ExampleSingleton alloc] init];
});
[_sharedInstance resetTimer];
return _sharedInstance;
}
......
//其余代码和以上一样
......
- (void)resetTimer {
if (_timer.isValid) {
[_timer invalidate];
NSLog(@"Reset Timer");
}
_timer= [NSTimer scheduledTimerWithTimeInterval:600 target:_sharedInstance selector:@selector(timeEndHaltSharedInstance) userInfo:nil repeats:NO];
}
第三版代码在每次重置前,查询是否存在计时器,有的话就使用invalidate函数释放掉旧的计时器.算是完整实现功能了
可是改了这么久,发现绕了一个大弯,无非是想及时释放旧的计时器,从而防止内存溢出,关键在于,使用了成员方法,让计时器本身被_sharedInstance产生retain.所以就去尝试使用了类方法.
使用了类方法代替成员方法
+ (instancetype)shareInstance {
dispatch_once(&_onceToken, ^{
_sharedInstance = [[ExampleSingleton alloc] init];
});
_timer= [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(haltSharedInstance) userInfo:nil repeats:NO];
return _sharedInstance;
}
+ (void)haltSharedInstance {
NSLog(@"SharedInstance Did Halted By Time ");
if (_sharedInstance) {
_sharedInstance = nil;
_onceToken = 0;
}
}
输出日志如下
2016-01-12 17:21:48.079 Learn[32311:6674061] access ShareInstance 0x7fd9f96533f0
2016-01-12 17:21:49.255 Learn[32311:6674061] access ShareInstance 0x7fd9f96533f0
2016-01-12 17:21:49.935 Learn[32311:6674061] access ShareInstance 0x7fd9f96533f0
2016-01-12 17:21:58.084 Learn[32311:6674061] SharedInstance Did Halted By Time
2016-01-12 17:21:58.084 Learn[32311:6674061] SharedInstance Did Halted
2016-01-12 17:21:59.258 Learn[32311:6674061] SharedInstance Did Halted By Time
2016-01-12 17:21:59.939 Learn[32311:6674061] SharedInstance Did Halted By Time
发现如果使用类方法,发现scheduledTimerWithTimeInterval中的类方法不会对SharedInstance产生retain,使得第一个计时器到时间就会终止掉单例.说明旧的计时器还是没有被释放掉.
所以才有了最终代码,打印日志如下
2016-01-12 17:34:22.452 Learn[32375:6683898] ExampleSingleton ShareInstance Did Create 0x7fa20a346e90
2016-01-12 17:34:22.453 Learn[32375:6683898] Access ShareInstance 0x7fa20a346e90
2016-01-12 17:34:23.403 Learn[32375:6683898] Access ShareInstance 0x7fa20a346e90
2016-01-12 17:34:23.403 Learn[32375:6683898] SharedInstance Reset Timer
2016-01-12 17:34:25.796 Learn[32375:6683898] Access ShareInstance 0x7fa20a346e90
2016-01-12 17:34:25.797 Learn[32375:6683898] SharedInstance Reset Timer
2016-01-12 17:34:35.797 Learn[32375:6683898] SharedInstance Will Halted
2016-01-12 17:34:35.797 Learn[32375:6683898] SharedInstance Did Halted
因为**[_timer invalidate]**仅仅是让倒计时触发停止,是不是真的被释放了内存呢?如果没有释放,会不会造成内存溢出?
+ (void)resetTimer {
if (_timer.isValid) {
[_timer invalidate];
_timer = nil;
NSLog(@"SharedInstance Reset Timer");
}
//break point 此处打断点
_timer= [NSTimer scheduledTimerWithTimeInterval:600 target:self selector:@selector(haltSharedInstance) userInfo:nil repeats:NO];
}
使用以上代码进行控制台调试lldb进行验证
2016-01-12 23:01:26.669 Learn[33017:6772455] ExampleSingleton ShareInstance Did Create 0x7ff378d10020
2016-01-12 23:01:26.670 Learn[33017:6772455] Access ExampleSingleton 0x7ff378d10020
(lldb) po _timer//1. timer未被初始化
nil
(lldb) n
(lldb) po _timer//2. timer初始化成功 地址一
<__NSCFTimer: 0x7ff37b0028a0>
(lldb) po 0x7ff37b0028a0//3. 验证地址一内的内容
<__NSCFTimer: 0x7ff37b0028a0>
(lldb) c//4. 继续执行 第二次触发断点
2016-01-12 23:01:53.404 Learn[33017:6772455] Access ExampleSingleton 0x7ff378d10020
2016-01-12 23:01:53.404 Learn[33017:6772455] ExampleSingleton Reset Timer
(lldb) po 0x7ff37b0028a0 //5. 打印地址一,发现仅为地址,没有任何变量
140683717388448
(lldb) po _timer//6. 再次检查timer,没有任何指向
nil
(lldb) n//7. 向下执行一行,进行初始化
(lldb) po _timer//8. 第二次初始化成功,地址二出现
<__NSCFTimer: 0x7ff378e12a80>
(lldb) p 0x7ff378e12a80 //地址二的位置
(long) $7 = 140683681802880
(lldb) p 0x7ff37b0028a0//地址一的位置
(long) $8 = 140683717388448
发现如果进行无效后指向nil,第一次初始化的地址会被释放.使用上文中的最终版代码进行验证
2016-01-12 23:12:53.624 Learn[33048:6777254] ExampleSingleton ShareInstance Did Create 0x7ff4f041c980
2016-01-12 23:12:53.625 Learn[33048:6777254] Access ExampleSingleton 0x7ff4f041c980
(lldb) po _timer//1. timer未被初始化
nil
(lldb) n
(lldb) po _timer//2. timer初始化成功 地址一
<__NSCFTimer: 0x7ff4f0514580>
(lldb) po 0x7ff4f0514580//3. 验证地址一内的内容
<__NSCFTimer: 0x7ff4f0514580>
(lldb) c //4. 继续执行 第二次触发断点
Process 33048 resuming
2016-01-12 23:13:19.084 Learn[33048:6777254] Access ExampleSingleton 0x7ff4f041c980
2016-01-12 23:13:19.084 Learn[33048:6777254] ExampleSingleton Reset Timer
(lldb) po 0x7ff4f0514580 //5. 打印地址一,发现变量未被释放
<__NSCFTimer: 0x7ff4f0514580>
(lldb) po _timer//6. 再次检查timer,发现指向的仍为地址一,仅仅是从新启动了倒计时
<__NSCFTimer: 0x7ff4f0514580>
经过验证发现,如果仅仅**[_timer invalidate]**,静态指针指向的NSTimer并没有被释放,仅仅是停止了倒计时,下一次初始化时,还是在原地址,从新打开了新的倒计时.
这里分析是错误的,确实没有引起循环引用,但是对于原因的定位是错误的,请参考以下文件
Block其实就是一段代码段,假设我在ClassA中使用ClassB的Block,block实际存在于ClassA的内存段p:0xXXXXX.也就是说在运行的时候如果ClassB的Block被使用,他会等待Block的代码段出现,如下.
typedef void(^CompletionBlock)(NSString *message);
//ClassB.h
- (void)classBFunction:(CompletionBlock)completion;
//ClassB.m
- (void)classBFunction:(CompletionBlock)completion {
completion(@"wait Block");
}
此时,我在ClassA中声明ClassB,并且调用该函数
//ClassA.m
ClassB *b = [[ClassB alloc]init];
[b classBFunction:^(NSString *message) {
NSLog(@"%@",message);
}];
此时**NSLog(@”%@”,message)其实是ClassA中的代码片段,处于地址p.也就是说如果ClassB想在classBFunction:**内部completion执行传递来的NSLog,那么ClassA的内存千万不能被释放.也就是说ClassB必须用Block对ClassA产生一个Retain,一个强引用.
Block一共分三种
示例
BlkSum blk1 = ^ long (int a, int b) {
return a + b;
};
NSLog(@"blk1 = %@", blk1);// blk1 = <__NSGlobalBlock__: 0x47d0>
int base = 100;
BlkSum blk2 = ^ long (int a, int b) {
return base + a + b;
};
NSLog(@"blk2 = %@", blk2); // blk2 = <__NSStackBlock__: 0xbfffddf8>
BlkSum blk3 = [[blk2 copy] autorelease];
NSLog(@"blk3 = %@", blk3); // blk3 = <__NSMallocBlock__: 0x902fda0>
在较早版本的Xcode,由于Block会对.m本身的ClassA产生一个Retain,而ClassA如果想调用ClassB的Block,自然也会对ClassB产生一个Retain.所以Block作为传送指针的通道,就让AB互相强引用了,例如Bang的文献里说的.
@interface ClassAViewController ()
@property (nonatomic, strong) ClassB *classB;
@end
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
_classB = [[ClassB alloc]init];
[_classB blockFunction:^(UIView *view) {
[self completionBlock];
}];
}
- (void)completionBlock {
NSLog(@"ClassAViewController Comlpetion Block");
}
- (void)dealloc {
NSLog(@"ClassAViewController did dealloc");
}
由于testFunction的block要调用ClassA(self)里的函数,为了防止这一段函数被释放,必须对ClassA产生一个Retain.在以前的版本为了避免产生循环引用,会使用一个weak指针指向self,然后通过weak指针调用self的函数.
_b = [[ClassB alloc]init];
id weakself = self;
[_b testFunction:^(UIView *view) {
[weakself completionBlock];
}];
经过Xcode7.2实际测试,这样书写已经不会产生循环引用,dealloc会被正常调用
还有一种可能的情况,有3个类形成通过block循环引用,但是很少见.
+-----------+ +-----------+ +-----------+
| self | | obj | | Block |
| ClassA | --------> | ClassC | --------> | ClassB |
| retain 1 | | retain 1 | | retain 1 |
| | | | | |
+-----------+ +-----------+ +-----------+
^ |
| strong |
+------------------------------------------------+
我去专门复现也无法复现出来,因为ClassC中调用ClassB的Block,访问的是ClassC的内存段,ClassB基本很少有手段对ClassA产生Retain.因为总需要一个指针把ClassA传递给ClassB,如果使用弱指针传递,就相当旧版本时的weakself了
+-----------+ +-----------+ +-----------+
| self | | obj | | Block |
| ClassA | --------> | ClassC | --------> | ClassB |
| retain 1 | | retain 1 | | retain 1 |
| | | | | |
+-----------+ +-----------+ +-----------+
^ |
| weak |
+---------------------------+
不是,与其担心Block造成循环引用,更应该关心透穿时使用什么指针
+-----------+ +-----------+ +-----------+
| self | strong | obj | strong | Block |
| ClassA | --------> | ClassC | --------> | ClassB |
| retain 1 | ?pointA | retain 1 | pointA | retain 1 |
| | <---------| | --------> | |
+-----------+ +-----------+ +-----------+
^ |
| weak |
+---------------------------+
在上述过程中,pointA应该使用weak,如果使用了strong,ClassA对ClassC本身就是强引用,如果pointA也是强引用,就形成了循环引用,这种前后两个界面互相传递的情况比较常见,一不小心就会形成循环引用,这也是为什么推荐使用代码洁癖weak的prorerty的原因.
+-----------+ +-----------+
| self | strong | obj |
| ClassA | ---------------> | ClassC |
| retain 1 | strong-pointA | retain 1 |
| | <---------------- | |
+-----------+ +-----------+
//ClassA.h
@interface ClassAViewController : UIViewController
- (void)completionBlock;
@end
//ClassA.m
#import "ClassCViewController.h"
@interface ClassAViewController ()
@property (nonatomic, strong) UIButton *btn;
@property (nonatomic, strong) ClassCViewController *classCVC;
@property (nonatomic, strong) UIButton *dismissBtn;
@end
@implementation ClassAViewController
- (void)touchUpInsideButton:(UIButton *)button {
_classCVC = [[ClassCViewController alloc]init];
_classCVC.classAVC = self;
[self presentViewController:_classCVC animated:YES completion:nil];
}
- (void)touchUpInsideDismissBtn:(UIButton *)button {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)completionBlock {
NSLog(@"ClassAViewController Comlpetion Block");
}
- (void)dealloc {
NSLog(@"ClassAViewController did dealloc%p",self);
}
@end
//ClassC.h
@interface ClassCViewController : UIViewController
//此处是pointA,使用了Strong,形成循环引用
@property (nonatomic, strong) ClassAViewController *classAVC;
@end
//ClassC.m
#import "ClassB.h"
#import "ClassAViewController.h"
@interface ClassCViewController ()
@property (nonatomic, strong) UIButton *btn;
@property (nonatomic, strong) ClassB *classB;
@end
@implementation ClassAViewController
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
_classB = [[ClassB alloc]init];
[_classB blockFunction:^(UIView *view) {
[_classAVC completionBlock];
}];
}
- (void)touchUpInsideButton:(UIButton *)button {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)dealloc {
NSLog(@"ClassCViewController did dealloc%p",self);
}
@end
//ClassB.h
@interface ClassB : NSObject
- (void)blockFunction:(void(^)(UIView *view))completion;
@end
//ClassB.m
- (void)blockFunction:(void(^)(UIView *view))completion {
UIView *view = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 50, 50)];
[view setBackgroundColor:[UIColor redColor]];
completion(view);
}
使用了局部变量的NSStackBlock,对局部变量采用的是传值形式的调用,进入Block后更改不会影响原变量.如果想进行传址,需要加上**__block**标注
__block int base = 100;
BlkSum blk2 = ^ long (int a, int b) {
base += 10;
return base + a + b;
};
NSLog(@"blk2 = %@", blk2); // blk2 = <__NSStackBlock__: 0xbfffddf8>
[1] http://git.devzeng.com/blog/ios-arc-block-retain-cycle.html
[2] http://tanqisen.github.io/blog/2013/04/19/gcd-block-cycle-retain/
[3] http://git.devzeng.com/blog/ios-arc-block-retain-cycle.html
#define HSCoder @"汉斯哈哈哈"
static const NSString *HSCoder = @"汉斯哈哈哈";
const NSString *HSCoder = @"汉斯哈哈哈";
NSString const *HSCoder = @"汉斯哈哈哈";
NSString *const HSCoder = @"汉斯哈哈哈";
结论:const右边的不能被修改
const NSString *HSCoder = @"汉斯哈哈哈";//1. "*HSCoder"不能被修改, "HSCoder"能被修改
NSString const *HSCoder = @"汉斯哈哈哈";//2. "*HSCoder"不能被修改, "HSCoder"能被修改
NSString * const HSCoder = @"汉斯哈哈哈";//3. "HSCoder"不能被修改,"*HSCoder"能被修改
从以上可以知道情况1和2相同,没有区别.
如果一个局部常量在外部进行访问,编译时会提示找不到符号的错误
const NSString *HSCoder = @"汉斯哈哈哈";//全局常量字符串
static const NSString *HSCoder = @"汉斯哈哈哈";//局部常量字符串
UIKIT_EXTERN相当于extern定义,表示扩展的全局常量,凡是定义了UIKIT_EXTERN的,只要包含了UIKit这个库,就可以访问这个常量.所以Apple本身常用以下这种形式定义在头文件.
UIKIT_EXTERN NSString *const HSCoder;
代表的意思是
进行了这样的定义后,一般会在.m里实现真正的文本内容,由于条件2,在进行赋值的时候HSCoder = @”新内存”,HSCoder无法改变指向的内存地址,所以无法赋值.
NSString *const HSCoder = @"汉斯哈哈哈";
由于NSNumber是个Object通过指针调用,而C类型是基本类型,是直接调用内存.
- (NSNumber *)initWithChar:(char)value NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithUnsignedChar:(unsigned char)value NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithShort:(short)value NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithUnsignedShort:(unsigned short)value NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithInt:(int)value NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithUnsignedInt:(unsigned int)value NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithLong:(long)value NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithUnsignedLong:(unsigned long)value NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithLongLong:(long long)value NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithUnsignedLongLong:(unsigned long long)value NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithFloat:(float)value NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithDouble:(double)value NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithBool:(BOOL)value NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithInteger:(NSInteger)value NS_AVAILABLE(10_5, 2_0) NS_DESIGNATED_INITIALIZER;
- (NSNumber *)initWithUnsignedInteger:(NSUInteger)value NS_AVAILABLE(10_5, 2_0) NS_DESIGNATED_INITIALIZER;
除了写NSNumber的初始化类之外,还可以通过基本类型前加”@”快速转换.
int a = 1;
NSLog(@"%d",a);
NSLog(@"%@",@2);
id str = @3;
NSLog(@"%@",str);
NSNumber *num = [[NSNumber alloc]initWithInt:@"4"];
NSLog(@"%@",num);
例如,示例代码中str本身就是个NSNumber的类,由”@3”快速转换而成.
现在客户端和服务器端之间的数据交换格式基本都是JSON,转换的时候要注意一些坑坑( ╯-_-)╯~┴—┴
OC中的空是nil,而Java中的空是null,nil代表指针不指向任何地址,内容为空,null就是代表真的空,C语言上的空,什么都没有,所以说当服务器端传来一个空字符串.
{"string":"null"}
在OC中被解析的时候,一般是先转化成Dictionary,然后用Model的NSString接收.
NSString *string = Dictionary[@"string"];
此时判断string是否为空会用
if([string isEqualToString:@""]) {
//Do Something
}
这个时候,判断永远是NO的,因为string指向的内存根本不是NSString,而是一个NSNull,Json序列化的时候null会被序列化成NSNull而不是空的NSString.
语言里都是有保留关键字的,所以说不能用关键字作为模型的属性,比如说new,因为JSON串中只是字符串,可以使用new,如果转化成Model的时候,就不能用服务端给出的字段作为属性了.
这个时候能找到服务端的写代码的,就去把他打一顿,找不到..就自己默默改然后加上备注就好了.
代码具有一致性,一般都比较便于阅读和管理,每个人不一样,但是总之要整洁比较好,我的习惯是
以下是代码示范
@interface BPLoginMainViewController ()
@property (nonatomic, weak) id name;//指向控件的弱指针
@end
@implementation BPLoginMainViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//初始化数据
[self dataInital];
//
[self instituteSubviews];//初始化控件
[self constrainSubviewsLayout];//对控件进行约束
[self modifySubviewsPattern];//对控件进行样式调整
//添加动作/通知/手势等
[self addActionAndNotification];
}
- (void)dataInital {
}
#pragma mark - View Layout Pattern
- (void)instituteSubviews {
//Init Subviews
<#type#> *<#name#> = [[<#type#> alloc]init];
//Weak Point
_<#name#> = <#name#>;
//Add Subviews
[<#view#> addSubview:_<#name#>];
}
- (void)constrainSubviewsLayout {
//Set autorizingMask
[_<#name#> setTranslatesAutoresizingMaskIntoConstraints:NO];
//AutoLayout VF
NSDictionary *views = NSDictionaryOfVariableBindings(_<#name#>);
NSDictionary *metrics = @{};
NSMutableArray *constraints = [NSMutableArray array];
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"" options:0 metrics:metrics views:views]];
[<#view#> addConstraints:constraints];
//不通过数组直接添加约束数组
// [<#view#> addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"" options:0 metrics:metrics views:views]];
//Single添加单个约束
// [<#view#> addConstraint:[NSLayoutConstraint constraintWithItem:_<#name#> attribute:<#attribute#> relatedBy:<#relation#> toItem:_<#view#> attribute:<#attribute#> multiplier:1.0 constant:0]];
}
- (void)modifySubviewsPattern {
//Modify Pattern
}
#pragma mark - Action 添加所有动作和动作响应
- (void)addActionAndNotification {
//Action
[<#name#> addTarget:<#target#> action:<#action#> forControlEvents:<#UIControlEventTouchUpInside#>]
//Notification
[[NSNotificationCenter defaultCenter] addObserver:<#target#> selector:<#action#> name:<#NSNotification#> object:<#object#>];
//Gesture
UITapGestureRecognizer *<#gesture#> = [[UITapGestureRecognizer alloc]initWithTarget:<#target#> action:<#action#>];
}
@end
调用present函数一般在ViewController里调用
UIViewController *vc = [[UIViewController alloc] init];
[self presentViewController:vc animated:YES completion:nil];
如果是NavigationController里的ViewController,是加入了NavigationController.viewControllers还是独立的一个Window?
// The view controller that was presented by this view controller or its nearest ancestor.
@property(nullable, nonatomic,readonly) UIViewController *presentedViewController NS_AVAILABLE_IOS(5_0);
// The view controller that presented this view controller (or its farthest ancestor.)
@property(nullable, nonatomic,readonly) UIViewController *presentingViewController NS_AVAILABLE_IOS(5_0);
由代码知道,其本身不在任何一个Array里,而是自身形成了一个链表.presentedViewController是指其present出来的VC,presentingViewController是指谁presented了当前VC.和VC当前VC本身是Tabbar还是Navigation还是普通VC没有关系.
创造Model,就是把标准数据格式,转化成一个类,一般模型Model对应的是字典Dictionary.以便于修改和调用.Model可以用”.”语法调用,且可以赋值和修改,Dictionary如果不是mutable的话,在初始化后就不能改变了,调用还要记得key值
//Model.h
@interface CustomModel : NSObject<NSCopying>
@property (nonatomic, copy) NSString *theme;
@property (nonatomic, copy) NSString *mark;
- (instancetype)initWithDict:(NSDictionary *)dict;
+ (NSDictionary *)convertDictFromModel:(CustomModel *)model;
+ (instancetype)createWithDict:(NSDictionary *)dict;
@end
//Model.m
@implementation CustomModel
- (instancetype)initWithDict:(NSDictionary *)dict {
self = [super init];
if (self) {
self.theme = [dict valueForKey:@"THEME"];
self.mark = [dict valueForKey:@"MARK"];
}
return self;
}
+ (NSDictionary *)convertDictFromModel:(CustomModel *)model {
[dict setValue:model.theme forKey:@"THEME"];
[dict setValue:model.mark forKey:@"MARK"];
return dict;
}
+ (instancetype)createWithDict:(NSDictionary *)dict {
return [[self alloc]initWithDict:dict];
}
- (id)copyWithZone:(NSZone *)zone {
CustomModel *newItem = [[CustomModel allocWithZone:zone] init];
newItem.theme = self.theme;
newItem.mark = self.mark;
return newItem;
}
@end
数据主要是指通用化的结构数据,比如Array和Dictionary,模型是指自己自定义的Object
这个就和Json一样,都是基本类型,很容易解析
数据和模型之间嵌套,因为不能被标准库解析模型,所以一定要加入模型的.h文件,用来解析和使用
- (instancetype)initWithDict:(NSDictionary *)dict;//字典转模型(写)
+ (NSDictionary *)convertDictFromModel:(CustomModel *)model;//模型转字典(读,解析)
+ (instancetype)createWithDict:(NSDictionary *)dict;//字典转模型
为了优化数据的结构,可以采用模型套模型,但是一定要记得加载两个模型的.h文件,以便使用模型.
有,我的老师说Github上有个她培训老师李明杰的第三放库,但是我没用过
MJextension
NSArray<__kindof NSDictionary *> *array;
//字典数组按KEY排序
+ (NSArray *)sortArray:(NSArray *)array withKey:(NSString *)key {
NSSortDescriptor *sortDesc = [NSSortDescriptor sortDescriptorWithKey:key ascending:YES];
NSArray *sortDescArr = [NSArray arrayWithObject:sortDesc];
NSArray *sortedArr = [array sortedArrayUsingDescriptors:sortDescArr];
return sortedArr;
}
Array是根据sortDescriptors进行排序,其本身是一个NSSortDescriptor的Array.所以只需要根据key来创建NSSortDescriptor,并放入一个数组就可以了.
@interface NSArray<ObjectType> (NSSortDescriptorSorting)
- (NSArray<ObjectType> *)sortedArrayUsingDescriptors:(NSArray<NSSortDescriptor *> *)sortDescriptors; // returns a new array by sorting the objects of the receiver
@end
@interface NSMutableArray<ObjectType> (NSSortDescriptorSorting)
- (void)sortUsingDescriptors:(NSArray<NSSortDescriptor *> *)sortDescriptors; // sorts the array itself
@end
tag:
缺失模块。
1、请确保node版本大于6.2
2、在博客根目录(注意不是yilia根目录)执行以下命令:
npm i hexo-generator-json-content --save
3、在根目录_config.yml里添加配置:
jsonContent: meta: false pages: false posts: title: true date: true path: true text: false raw: false content: false slug: false updated: false comments: false link: false permalink: false excerpt: false categories: false tags: true