iOS 沙盒机制及沙盒目录获取

1. iOS 沙盒机制

iOS 系统为每个iOS APP创建了一个独立的文件区域,该文件区域成为沙盒。该APP所有的非代码文件都只能保存在沙盒中,并且改APP不能访问其他APP的沙盒文件。

1.1 结构

沙盒的目录结构如下:

"应用程序包"
Documents
Library
    Caches
    Preferences
tmp
1.2说明

"应用程序包": 存放应用程序的源文件,包括资源文件和可执行文件。

Documents : 苹果建议将程序中建立的或在程序中浏览到的文件数据保存在该目录下,iTunes备份和恢复的时候会包括此目录

Library : 存储程序的默认设置或其它状态信息;

Library/Caches : iTunes不会同步此文件夹,适合存储体积大,不需要备份的非重要数据。此目录下文件不会在应用退出删除

Library/Preferences : 存放通过NSUserDefaults来保存的配置信息。iTunes同步该应用时会同步此文件夹中的内容。

tmp : 提供一个即时创建临时文件的地方。

2. 沙盒目录获取

获取"应用程序包"目录 :

NSString *path = [[NSBundle mainBundle] bundlePath];
NSLog(@"%@", path);

获取Documents目录 :

NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"%@", path);

获取Library/Caches目录 :

NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"%@", path);

获取tmp目录 :

NSString *path = NSTemporaryDirectory();
NSLog(@"%@", path);

iOS 数据持久化

所谓的持久化,就是将数据保存到硬盘中,使得在应用程序或机器重启后可以继续访问之前保存的数据。在iOS开发中,有很多数据持久化的方案,接下来我将尝试着介绍一下5种方案:

  1. Plist文件(属性列表)
  • Preference(偏好设置)
  • NSKeyedArchiver(归档)
  • SQLite 3
  • CoreData
1. Plist文件

Plist文件是将某些特定的类,通过XML文件的方式保存在目录中。

可以被序列化的类型只有如下几种:

NSArray;
NSMutableArray;
NSDictionary;
NSMutableDictionary;
NSData;
NSMutableData;
NSString;
NSMutableString;
NSNumber;
NSDate;

其他类型需要转化为以上几种类型才能通过plist进行持久化。

使用方法:

  1. 可视化添加数据
    通过在XCode中进行添加数据
  2. 代码添加读取数据
//获得文件路径
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *fileName = [path stringByAppendingPathComponent:@"fileName.plist"];

//存储数据
NSArray *array = @[@"123", @"456", @"789"];
[array writeToFile:fileName atomically:YES];

//读取数据
NSArray *result = [NSArray arrayWithContentsOfFile:fileName];
NSLog(@"%@", result);

注意事项

  • 只有以上列出的类型才能使用plist文件存储。
  • 存储时使用writeToFile: atomically:方法。 其中atomically表示是否需要先写入一个辅助文件,再把辅助文件拷贝到目标文件地址。这是更安全的写入文件方法,一般都写YES。
  • 读取时使用arrayWithContentsOfFile:方法。
2. Preference

Preference该方法是通过系统提供的统一方法存储APP的一些配置信息,文件保存在Library/Preferences目录下。

使用方法:

//1.获得NSUserDefaults文件
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
//2.向文件中写入内容
[userDefaults setObject:@"AAA" forKey:@"a"];
[userDefaults setBool:YES forKey:@"sex"];
[userDefaults setInteger:21 forKey:@"age"];
//2.1立即同步
[userDefaults synchronize];
//3.读取文件
NSString *name = [userDefaults objectForKey:@"a"];
BOOL sex = [userDefaults boolForKey:@"sex"];
NSInteger age = [userDefaults integerForKey:@"age"];
NSLog(@"%@, %d, %ld", name, sex, age);

注意事项

  • 偏好设置是专门用来保存应用程序的配置信息的,一般不要在偏好设置中保存其他数据。
  • 如果没有调用synchronize方法,系统会根据I/O情况不定时刻地保存到文件中。所以如果需要立即写入文件的就必须调用synchronize方法。
  • 偏好设置会将所有数据保存到同一个文件中。即preference目录下的一个以此应用包名来命名的plist文件。
3. NSKeyedArchiver

归档在iOS中是另一种形式的序列化,只要遵循了NSCoding协议的对象都可以通过它实现序列化。由于决大多数支持存储数据的Foundation和Cocoa Touch类都遵循了NSCoding协议,因此,对于大多数类来说,归档相对而言还是比较容易实现的。

  1. 遵循NSCoding协议
    NSCoding协议声明了两个方法,这两个方法都是必须实现的。一个用来说明如何将对象编码到归档中,另一个说明如何进行解档来获取一个新对象。
//1.遵循NSCoding协议 
@interface Person : NSObject   

//2.设置属性
@property (nonatomic, strong) UIImage *avatar;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;

@end

@implementation Person
//解档
- (id)initWithCoder:(NSCoder *)aDecoder {
    if ([super init]) {
        self.avatar = [aDecoder decodeObjectForKey:@"avatar"];
        self.name = [aDecoder decodeObjectForKey:@"name"];
        self.age = [aDecoder decodeIntegerForKey:@"age"];
    }
    return self;
}
//归档
- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:self.avatar forKey:@"avatar"];
    [aCoder encodeObject:self.name forKey:@"name"];
    [aCoder encodeInteger:self.age forKey:@"age"];
}
@end

注意事项

  • 如果需要归档的类是某个自定义类的子类时,就需要在归档和解档之前先实现父类的归档和解档方法。即 [super encodeWithCoder:aCoder] 和 [super initWithCoder:aDecoder] 方法;
  • 可以使用runtime来实现归档,可以使代码更加简洁、清晰,同时在自定义类的子类实现归档时,可以获取到父类的属性进行归档。

使用方法:

需要把对象归档是调用NSKeyedArchiver的工厂方法 archiveRootObject: toFile: 方法。

NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.data"];
Person *person = [[Person alloc] init];
person.avatar = self.avatarView.image;
person.name = self.nameField.text;
person.age = [self.ageField.text integerValue];
[NSKeyedArchiver archiveRootObject:person toFile:file];

需要从文件中解档对象就调用NSKeyedUnarchiver的一个工厂方法 unarchiveObjectWithFile: 即可。

NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.data"];
Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:file];
if (person) {
    self.avatarView.image = person.avatar;
    self.nameField.text = person.name;
    self.ageField.text = [NSString stringWithFormat:@"%ld", person.age];
}

注意事项

  • 必须遵循并实现NSCoding协议
  • 保存文件的扩展名可以任意指定
  • 继承时必须先调用父类的归档解档方法

iOS 读取文件四种方式

iOS 读取文件总结起来有以下四种方式:

iOS 读取文件内容步骤 :

  1. 获取文件位置
  2. 获取文件内容
//获取文件位置
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *filePath = [paths lastObject];
filePath = [filePath stringByAppendingPathComponent:@"readFile.txt"];
NSLog(@"文件位置 : %@", filePath);

//1. 通过 NSString 直接读取数据
NSString *fileContent = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
NSLog(@"%@", fileContent);

//2. 通过 NSData 读取数据
NSData *data = [NSData dataWithContentsOfFile:filePath];
fileContent = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@", fileContent);

//3. 通过 NSFileManager 读取数据
NSFileManager *fm = [NSFileManager defaultManager];
data = [fm contentsAtPath:filePath];
fileContent = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@", fileContent);

//4. 通过 NSFileHandle 读取数据
NSFileHandle *fh = [NSFileHandle fileHandleForReadingAtPath:filePath];
data = [fh readDataToEndOfFile];
fileContent = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@", fileContent);

总结 : 第一种方式直接读取数据最简单,其他方式都是需要转换为NSData,然后再通过NSString来获取文件内容

iOS 中延时执行的四种方法及其比较

在iOS开发中,有时会用到延时执行,下面就开发中会用到的四种延时执行的方法进行简单对比,以期后续可以更好的选择合适的延时方式。

需要延时执行的方法:

- (void)delayMethod {
    NSLog(@"execute : %@", [NSDate date]);
}
阻塞式延时

阻塞式延时会阻塞当前线程,为避免卡住用户界面,建议放在子线程执行

  • NSThread
NSLog(@"start : %@", [NSDate date]);
[NSThread sleepForTimeInterval:3.f];
//[NSThread exit];

[self delayMethod];
非阻塞式延时
  • performSelector
[self performSelector:@selector(delayMethod) withObject:nil afterDelay:3];
NSLog(@"start : %@", [NSDate date]);

[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(delayMethod) object:nil];
  • NSTimer
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(delayMethod) userInfo:nil repeats:NO];
NSLog(@"start : %@", [NSDate date]);

[timer invalidate];
  • dispatch_after
NSLog(@"start : %@", [NSDate date]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    [self delayMethod];
});

Demo : iOS 中延时执行的四种方法及其比较

面向对象三大基本特性,五大基本原则

透切理解面向对象三大基本特性是理解面向对象五大基本原则基础.

三大特性是:封装,继承,多态

==所谓封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。==封装是面向对象的特征之一,是对象和类概念的主要特性。 简单的说,一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。在一个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。

==所谓继承是指可以让某个类型的对象获得另一个类型的对象的属性的方它支持按级分类的概念。==继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。 通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。继承概念的实现方式有二类:实现继承与接口继承。实现继承是指直接使用基类的属性和方法而无需额外编码的能力;接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;

==所谓多态就是指一个类实例的相同方法在不同情形有不同表现形式。==多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。

五大基本原则

单一职责原则SRP(Single Responsibility Principle)
是指一个类的功能要单一,不能包罗万象。如同一个人一样,分配的工作不能太多,否则一天到晚虽然忙忙碌碌的,但效率却高不起来。

开放封闭原则OCP(Open-Close Principle)
一个模块在扩展性方面应该是开放的而在更改性方面应该是封闭的。比如:一个网络模块,原来只服务端功能,而现在要加入客户端功能,
那么应当在不用修改服务端功能代码的前提下,就能够增加客户端功能的实现代码,这要求在设计之初,就应当将服务端和客户端分开,公共部分抽象出来。

替换原则(the Liskov Substitution Principle LSP)
子类应当可以替换父类并出现在父类能够出现的任何地方。比如:公司搞年度晚会,所有员工可以参加抽奖,那么不管是老员工还是新员工,
也不管是总部员工还是外派员工,都应当可以参加抽奖,否则这公司就不和谐了。

依赖原则(the Dependency Inversion Principle DIP) 具体依赖抽象,上层依赖下层。假设B是较A低的模块,但B需要使用到A的功能,
这个时候,B不应当直接使用A中的具体类: 而应当由B定义一抽象接口,并由A来实现这个抽象接口,B只使用这个抽象接口:这样就达到
了依赖倒置的目的,B也解除了对A的依赖,反过来是A依赖于B定义的抽象接口。通过上层模块难以避免依赖下层模块,假如B也直接依赖A的实现,那么就可能造成循环依赖。一个常见的问题就是编译A模块时需要直接包含到B模块的cpp文件,而编译B时同样要直接包含到A的cpp文件。

接口分离原则(the Interface Segregation Principle ISP)
模块间要通过抽象接口隔离开,而不是通过具体的类强耦合起来

转载自:面向对象三大基本特性,五大基本原则

Objective-C 总结

网络相关
  • 网络请求
  • 多线程处理
  • 远程推送
  • 断点下载与断点上传
  • Socket
  • SOAP和RESTApi
Runtime
  • 方法与参数获取
  • 方法的动态修改

    • 解决数组与字典的越界问题
    • 解决按钮的重复点击问题
RunLoop
  • 线程与进程
基础知识
动画
  • drawRect
  • 动画流畅的检查(Profile)
内存问题
  • 内存释放与循环引用
  • 内存泄漏检测(Profile)
  • 内存问题的定位
项目管理
聊天
  • LeanCloud

KVO使用及注意点

KVO(Key-Value-Observer)是iOS中一个重要的概念,与协议(Protocol)、通知中心(NSNotification)、代码块(Block)等一起构起iOS的==通知体系==。
KVO主要提供对对象的属性变化的监听,包括对象属性的初始化、对象的修改,通过KVO我们可以获得对象属性修改的即时通知,以此来做一些程序的必要动作。

使用篇

KVO可以对当前对象的属性进行监听,同时也可以对其他对象的属性进行监听。
使用方法

//添加监听
[被监听对象 addObserver:监听者 forKeyPath:@"被监听对象属性" options:NSKeyValueObservingOptionNew context:nil];

//移除监听
[被监听者 removeObserver:监听者 forKeyPath:@"被监听对象属性" context:nil];

//获取监听事件改变
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    
}
注意点

KVO添加监听和移除监听必须成对出现,并且在被监听对象销毁前,需要移除被监听对象的监听事件,否则在被监听对象销毁的时候导致程序崩溃。崩溃提示信息:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance *** of class Observer was deallocated while key value observers were still registered with it.

Demo : KVO使用及注意点

ijkplayer 编译集成笔记

ijkplayer是哔哩哔哩(Bilibili)开源的一款视频直播开源框架,对于网络推流,视频播放有很好的稳定性和良好的性能表现。这里主要记录下自己编译集成ijkplayer的使用体验。

环境配置:

Mac OS X 10.11.5

1. 准备工作

安装homebrew:MAC OSX上必备的软件包管理工具,有了它之后可以很方便的安装开发套件。

ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew install git
brew install yasm
2. 下载ijkplayer

首先我们在当前目录创建一个目录用来存放ijkplayer相关文件

mkdir ijkplayer
cd ijkplayer

然后我们将ijkplayer克隆到本地,输入以下命令:

//克隆ijkplayer到本地
git clone https://github.com/Bilibili/ijkplayer.git ijkplayer-ios
//进入ijkplayer-ios
cd ijkplayer-ios
//切换分支
git checkout -B latest k0.6.0
//下载ffmpeg和相关脚本
./init-ios.sh

cd ios
//clean
./compile-ffmpeg.sh clean
//编译
./compile-ffmpeg.sh all

编译成功截图

完成以上步骤,我们已经离成功不远了。

打开刚刚编译的文件夹

open .

我们就可以看到ijkplayer给我们准备的demo程序,小试身手一下吧。

ijkplayer ios项目集成(图文详细版)

SVN 命令行解决冲突问题

今天使用svn up更新文件时,提示Skipped '***' -- Node remains in conflict,并且把所有文件删了后重新拉取文件还是无法解决问题,经过不懈努力,终于在stackoverflow找到了解决方法。

在命令行里输入如下命令:

svn resolved <filename or directory that gives trouble>

使用svn up重新拉取文件,问题得到解决。