乱七八糟的iOS工具箱

iOS_Notes_01.png


查看某个目录下所有Objective-C文件中的代码行数:find . "(" -name "*.m" -or -name "*.mm" -or -name "*.h" ")" -print | xargs wc -l


手动在代码中加入警告:#warning XXX


Command + s 快捷键可以直接把模拟器当前屏幕正在显示的内容保存到桌面上。


git commit --amend 可以对上一次的提交信息做修改(一般用来修改commit信息)


通过storyboard或Xib创建的UIView是无法实现继承关系的。


从iOS9开始,当程序启动完毕那一刻,所有要显示的窗口必须要有根控制器,否则就会导致崩溃并报如下错误。

Application windows are expected to have a root view controller at the end of application launch
// 下面这段代码让窗口延迟2秒之后再显示出来(没有根控制器)就能保证程序不会崩溃。因为在程序启动完毕那一刻,没有要显示的窗口。
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    [self.window makeKeyAndVisible];
});

UIWindow的显示不需要借助任何其他控件。
初始化一个UIWindow,然后设置其hidden属性为NO,这个UIWindow就能够显示出来。


UITabBarController中UITabBar上的图片跟文字是由子控制器的 tabBarItem.imagetabBarItem.selectedImagetabBarItem.title 来决定的。

UITabBarController *rootTabBarController = [[UITabBarController alloc] init];

UITableViewController *childTableViewController = [[UITableViewController alloc] init];
childTableViewController.tabBarItem.title = @"首页";
childTableViewController.tabBarItem.image = [UIImage imageNamed:@"tabBar_icon"];
childTableViewController.tabBarItem.selectedImage = [UIImage imageNamed:@"tabBar_icon_selected"];
[rootTabBarController addChildViewController:childTableViewController];

凡是系统提供的方法或者属性后面带有 UI_APPEARANCE_SELECTOR 标识说明, 就可以通过appearance对象来统一设置。

NSMutableDictionary *tabBarTitleAttrributesForNormal = [NSMutableDictionary dictionary];
tabBarTitleAttrributesForNormal[NSFontAttributeName] = [UIFont systemFontOfSize:13];
tabBarTitleAttrributesForNormal[NSForegroundColorAttributeName] = [UIColor grayColor];
NSMutableDictionary *tabBarTitleAttrributesForSelected = [NSMutableDictionary dictionary];
tabBarTitleAttrributesForSelected[NSForegroundColorAttributeName] = [UIColor grayColor];

// 统一设置UITabBarItem上Normal状态和Selected状态下的文字大小和颜色
UITabBarItem *appearanceTabBarItem = [UITabBarItem appearance];
[appearanceTabBarItem setTitleTextAttributes:tabBarTitleAttrributesForNormal forState:UIControlStateNormal];
[appearanceTabBarItem setTitleTextAttributes:tabBarTitleAttrributesForSelected forState:UIControlStateSelected];

如果控件已经显示出来,再通过appearance对象来统一设置,是无效的。必须重新刷新控件才能起作用。

// 可以通过如下代码来重新刷新控制器的view以及所有子view
[self.view removeFromSuperview];
[[UIApplication sharedApplication].keyWindow addSubView:self.view];

提取App中图片资源的小工具: iOS-Images-Extractor


随机数函数arc4random_uniform(x),可以用来直接产生0~(x-1)范围内的随机数,省去了通过arc4random()函数再取模的运算。


R、G、B三基色的值都相同就是灰色,值越小灰度越深。


Log宏&&Color宏

// Log
#ifdef DEBUG
#define HFLog(...) NSLog(__VA_ARGS__)
#else
#define HFLog(...) 
#endif

// Color
#define HFColorA(R, G, B, A) [UIColor colorWithRed:(R)/255.0 green:(G)/255.0 blue:(B)/255.0 alpha:(A)/255.0]
#define HFColor(R, G, B) HFColorA((R), (G), (B), 255)
#define HFRandomColor HFColor(arc4random_uniform(256), arc4random_uniform(256), arc4random_uniform(256))

全局宏不但能定义在pch文件中,还能定义在 Build Setting -> Preprocessor Macros 中。
注意:定义在 Build Setting -> Preprocessor Macros 中的宏名字不能都是小写字母。


只要在UITabBarController中添加一个子控制器,就能在UITabBar上添加一个UITabBarItem。


UIButton的懒加载:如果不设置UIButton的title,UIButton中的titleLabel是不会被创建的。
同理,如果不设置UIButton的image,UIButton中的imageView也是不会被创建的。


[UIImage imageNamed:nil] 这行代码会产生 CUICatalog: Invalid asset name supplied: (null) 的警告。
[UIImage imageNamed:@""] 这行代码会产生 CUICatalog: Invalid asset name supplied: 的警告。


判断字符串是否有内容:if (string.length)
判断数组是否有内容:if (array.count)
判断字典是否有内容:if (dictionary.count)


通过懒加载方式可以确保一个对象在当前控制器的生命周期内只会被创建一次。
对于同一个控件对象来说,如果被父控件添加多次(addSubView:),并不会被重复添加,当第二次或者第N次被添加到父控件上去时,只会让这个控件摆在父控件的最前面。


判断一个对象的类型:
if (unkonwnView.class != NSClassFromString(@"UIButton"))
if ([NSStringFromClass(unkonwnView.class) isEqualToString:@"UIButton"])


通过重写 -layoutSubviews 方法来布局子控件(一般用来布局子控件的Frame)。
-layoutSubviews 方法中,首先要调用 [super layoutSubviews] 方法确保执行了父类的 -layoutSubviews 方法,然后再做一些操作才能覆盖掉之前的操作。


如何替换掉UITabBarController中的只读tabBar属性?
由于UITabBarController中的tabBar属性是只读属性,所以采用 self.tabBar = [[HFTabBar alloc] init]; 方法是行不通的。
可以采用KVC方式替换:[UITabBarController setValue:[[HFTabBar alloc] init] forKeyPath:@"tabBar"];


导航控制器(UINavigationController)的导航栏(UINavigationBar)上内容(UINavigationItem)是由导航控制器中栈顶控制器的导航项(UINavigationItem)来决定的。
UITabBarController底部的UITabBar中每一个UITabBarButton的内容是由子控制器的UITabBarItem来决定的。


通过 [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"imageName"]]; 这种方式创建的UIImageView的尺寸(frame.size)是与传入图片的大小一致的。
通过 [[UIImageView alloc] init]; 这种方式创建的UIImageView的尺寸(frame.size)是(0, 0)。想要达到上面方式的效果,可以在添加完图片之后调用 sizeToFit 方法。


UIButton一些注意点:
设置文字应使用 setTitle:forState: 方法。
设置图片应使用 setImage:forState: 方法。
设置背景图片应使用 setBackgroundImage:forState: 方法。

取出UIButton中的文字应使用 titleForState:currentTitle 方法。
取出UIButton中的图片应使用 imageForState:currentImage 方法。
取出UIButton中的背景图片应使用 backgroundImageForState:currentBackgroundImage 方法。

设置UIButton的大小等于其内容的大小可使用 sizeToFit 方法。


设置当前控制器的导航栏标题应使用 self.navigationItem.title = @"Title",不应该使用 self.title = @"Title"


通过UINavigationController push某个控制器(UIViewController *testViewController)时隐藏底部的UITabBar,设置 testViewController.hidesBottomBarWhenPushed = YES 即可。


导航栏会自动放大导航栏上的按钮点击范围。


统一所有导航栏上左边的“返回”按钮,可以自定义一个UINavigationController,然后重写 - pushViewController:animated: 方法。

// 拦截所有需要Push的子控制器,然后在非RootViewController的子控制器的导航栏左上角添加自定义的“返回”按钮
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
    if (self.childViewControllers.count > 0) {
        // 自定义导航栏左上角返回按钮
        UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
        [backButton setTitle:@"返回" forState:UIControlStateNormal];
        [backButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        [backButton setTitleColor:[UIColor redColor] forState:UIControlStateHighlighted];
        [backButton setImage:[UIImage imageNamed:@"imageName"] forState:UIControlStateNormal];
        [backButton setImage:[UIImage imageNamed:@"imageHighlightedName"] forState:UIControlStateHighlighted];
        // 下面两行代码的位置不能互换,因为首先要计算出按钮的frame,才能对其内边距进行处理。
        [backButton sizeToFit];
        backButton.contentEdgeInsets = UIEdgeInsetsMake(0, -20, 0, 0);
        [backButton addTarget:self action:@selector(onClickBackButton:) forControlEvents:UIControlEventTouchUpInside];

        viewController.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
    }

    // 等子控制器的“返回”按钮设置好之后,再执行Push操作
    [super pushViewController:viewController animated:animated];
}

- (void)onClickBackButton:(UIButton *)sender {
    [self popViewControllerAnimated:YES];
}

如果自定义了导航栏上左边的返回按钮,会导致右滑返回手势失效。可通过如下代码解决:

@interface HFNavigationController () <UIGestureRecognizerDelegate>

@end

@implementation HFNavigationController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.interactivePopGestureRecognizer.delegate = self;
}

#pragma mark - UIGestureRecognizerDelegate

// 手势识别器会调用这个代理方法来决定手势是否有效
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {  
    // 如果当前子控制器为rootViewController,则不需要右滑返回
    if (self.childViewControllers.count == 1) return NO;

    return YES;
}

导航栏右滑返回手势通过以下打印信息可知,在屏幕边缘的右滑返回手势是通过 UIScreenEdgePanGestureRecognizer 调用 _UINavigationInteractiveTransition 私有类的 handleNavigationTransition: 方法来实现的。
_UINavigationInteractiveTransition 其实就是 self.interactivePopGestureRecognizer.delegate

UIScreenEdgePanGestureRecognizer
target= <(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition>)

如果要实现全屏右滑返回,只需要自定义 UIPanGestureRecognizer,然后调用 _UINavigationInteractiveTransition 私有类的 handleNavigationTransition: 方法。

@interface HFNavigationController () <UIGestureRecognizerDelegate>
@end

@implementation HFNavigationController

- (void)viewDidLoad {
  UIPanGestureRecognizer *screenPanGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self.interactivePopGestureRecognizer.delegate 
action:@selector(handleNavigationTransition:)];
  [self.view addGestureRecognizer:screenPanGestureRecognizer];

  screenPanGestureRecognizer.delegate = self;  
  self.interactivePopGestureRecognizer.enable = NO;
}

// 手势识别器会调用这个代理方法来决定手势是否有效
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    // 如果当前子控制器为rootViewController,则不需要右滑返回
    if (self.childViewControllers.count == 1) return NO;

    return YES;
}

@end

通过以下代码给导航栏设置一个与原生导航栏几乎一样的背景图片,可以覆盖掉Push时候导航栏的颜色渐变效果。
[self.navigationBar setBackgroundImage:[UIImage imageNamed:@"navigationbarBackgroundWhite"] forBarMetrics:UIBarMetricsDefault];


UIScrollView的 bounds.origincontentOffset 的值是一样的。


UIScrollView的 contentInset 属性值会对 contentOffset 产生影响,比如设置一个UIScrollView的 contentInset 属性值为 UIEdgeInsetsMake(64, 0, 0, 0),那么当把UIScrollView往下拉并松手的时候,等待UIScrollView滑动静止,这时,UIScrollView上方就有64高度的内边距是无法被自动弹回去了。此时的 contentOffset.y 值就是 -64


如果一个UIScrollView所在的控制器是导航控制器UINavigationController的子控制器,那么这个UIScrollView就会自动存在20(隐藏导航栏)或者64(显示导航栏)的顶部内边距(contentInset)。

如果这个子控制器中有多个UIScrollView,那这种效果也只会作用在第一个UIScrollView身上。

如果不想要这种效果,可以设置这个子控制器的 automaticallyAdjustsScrollViewInsets 属性为 NO 即可。

UINavigationBar和UITabBar的半透明穿透效果就是对UIScrollView设置了内边距(contentInset)的方式来实现的。
如果当前控制器中即有UINavigationBar又有UITabBar,那么UIScrollView的 contentInset 默认为 UIEdgeInsetsMake(64, 0, 49, 0)
如果当前控制器中只有UINavigationBar,那么UIScrollView的 contentInset 系统默认为 UIEdgeInsetsMake(64, 0, 0, 0)
如果当前控制器中只有UITabBar,那么UIScrollView的 contentInset 系统默认为 UIEdgeInsetsMake(0, 0, 0, 0)


在storyboard或者xib中,一旦设置UIButton的image属性,那么这个UIButton的Type会自动从 System 变成 Custom


在storyboard或者xib中,想要让UILabel中的文字换行,只需要快捷键 option + Enter
在storyboard或者xib中,通过 \n 是无法达到换行效果的。
在代码中,通过 \n 才可以达到换行的效果。


iOS7以前,状态栏的Style设置是通过如下代码来实现的。

// UIStatusBarStyleLightContent
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault];

从iOS7开始,对于状态栏的Style,由当前控制器来决定。

- (UIStatusBarStyle)preferredStatusBarStyle { 
    // return UIStatusBarStyleLightContent;
    return UIStatusBarStyleDefault;  
}

上面第一种方式相对于第二种方式有两点麻烦之处:
如果当一个控制器进入到另一个控制器之后需要改变状态栏的Style,那么当返回到上一个控制器时,状态栏的Style又要重新改变回去。
此外,想要让第一种方式生效,还必须在 info.plist 中设置 View controller-based status bar appearanceNO


修改UITextField的光标颜色使用 textField.tintColor


简单的图文混排(左边文字+中间图片+右边文字):

NSMutableAttributedString *totalAttributedString = [[NSMutableAttributedString alloc] init];

//左边文字
NSString *leftString = @"左边文字";
NSMutableDictionary *leftAttributes = [NSMutableDictionary dictionary];
leftAttributes[NSForegroundColorAttributeName] = [UIColor blueColor];
leftAttributes[NSFontAttributeName] = [UIFont systemFontOfSize:15];
NSAttributedString *leftAttributedString = [[NSAttributedString alloc] initWithString:leftString attributes:leftAttributes];

// 右边文字
NSString *rightString = @"右边文字";
NSMutableDictionary *rightAttributes = [NSMutableDictionary dictionary];
rightAttributes[NSForegroundColorAttributeName] = [UIColor redColor];
rightAttributes[NSFontAttributeName] = [UIFont systemFontOfSize:15];
NSAttributedString *rightAttributedString = [[NSAttributedString alloc] initWithString:rightString attributes:rightAttributes];

// 中间图片
NSTextAttachment *middleTextAttachment = [[NSTextAttachment alloc] init];
middleTextAttachment.image = [UIImage imageNamed:@"xxx"];
middleTextAttachment.bounds = CGRectMake(0, 0, 20, 20);
NSAttributedString *middleAttributedString = [NSAttributedString attributedStringWithAttachment:middleTextAttachment];

[totalAttributedString appendAttributedString:leftAttributedString];
[totalAttributedString appendAttributedString:middleAttributedString];
[totalAttributedString appendAttributedString:rightAttributedString];

UILabel *testLabel = [[UILabel alloc] init];
testLabel.frame = CGRectMake(100, 100, 0, 0);
testLabel.attributedText = totalAttributedString;
[testLabel sizeToFit];

设置UITextField的占位文字placeholder颜色的三种方法:

// 通过设置UITextField类中的attributedPlaceholder属性
UITextField *testTextField;
testTextField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:self.placeholder 
attributes:@{NSForegroundColorAttributeName : [UIColor whiteColor]}];
// 通过子类化UITextField并重写UITextField类中的方法 -drawPlaceholderInRect:
- (void)drawPlaceholderInRect:(CGRect)rect {
    [self.placeholder drawAtPoint:CGPointMake(0, (rect.size.height - self.font.lineHeight) * 0.5) 
    withAttributes:@{NSForegroundColorAttributeName : [UIColor whiteColor]}];
}
UITextField *testTextField;  
[testTextField setValue:[UIColor whiteColor] forKeyPath:@"placeholderLabel.textColor"];

利用分类给UITextField添加placeholderColor属性,从而方便修改占位文字颜色:

#import "UITextField+placeholderColor.h"

static NSString * const HFPlaceholderColorKey = @"placeholderLabel.textColor";

@implementation UITextField (placeholderColor)

- (void)setPlaceholderColor:(UIColor *)placeholderColor {
    NSString *tempPlaceholder = self.placeholder;
    self.placeholder = @" ";
    self.placeholder = tempPlaceholder;

    if (placeholderColor == nil) {
        placeholderColor = [UIColor colorWithRed:0 green:0 blue:0.0980392 alpha:0.22];
    }

    [self setValue:placeholderColor forKeyPath:HFPlaceholderColorKey];
}

- (UIColor *)placeholderColor {
    return [self valueForKeyPath:HFPlaceholderColorKey];
}

@end

获取某个类的所有成员变量,比如通过以下代码可以获取UITextField类中的所有成员变量:

unsigned int count;
Ivar *ivarList = class_copyIvarList([UITextField class], &count);
for (int i = 0; i < count; i++) {
    Ivar ivar = ivarList[i];
    NSLog(@"%s", ivar_getName(ivar));
}
free(ivarList);

在枚举类型中,通常可以看到一些左移 << 运算符,这表明这些枚举值可以组合使用。

// 这边截取了UIControlEvents中一部分关于UITextField的枚举值
typedef NS_OPTIONS(NSUInteger, UIControlEvents) {
    UIControlEventEditingDidBegin      = 1 << 16,     // UITextField
    UIControlEventEditingChanged       = 1 << 17,
    UIControlEventEditingDidEnd        = 1 << 18,
    UIControlEventEditingDidEndOnExit  = 1 << 19,     // 'return key' ending editing
};

// 当testTextField开始编辑或者结束编辑的时候,都会调用 editing: 方法
UITextField *testTextField;
[testTextField addTarget:self action:@selector(editing:) forControlEvents:UIControlEventEditingDidBegin |  UIControlEventEditingDidEnd];

原理:通过位运算符 |& 实现枚举值的组合与分解。

NSInteger a = 1 << 0;
NSInteger b = 1 << 1;
NSInteger c = 1 << 2;

NSInteger mixedValue = a | b;

NSLog(@"%ld, %ld, %ld", mixedValue & a, mixedValue & b, mixedValue & c);  // 1, 2, 0

UITextField的四种样式:

typedef NS_ENUM(NSInteger, UITextBorderStyle) {
    UITextBorderStyleNone,
    UITextBorderStyleLine,
    UITextBorderStyleBezel,
    UITextBorderStyleRoundedRect
};

通过storyboard或xib创建的UITextField默认样式是UITextBorderStyleRoundedRect。
通过代码创建的UITextField默认样式是UITextBorderStyleNone。


监听UITextField事件的三种方式:

1、 addTarget:action:forControlEvents:

// UIControlEventEditingDidBegin  // 开始编辑(成为焦点)
// UIControlEventEditingChanged  // 正在编辑(改变文字)
// UIControlEventEditingDidEnd  // 结束编辑(失去焦点)
// UIControlEventEditingDidEndOnExit  // 确认编辑(点击了return按键)
UITextField *testTextField;
[testTextField addTarget:self action:@selector(onActionTextField:) forControlEvents:UIControlEventEditingDidBegin];

2、 [NSNotificationCenter defaultCenter] addObserver:selector:name:object:

// UITextViewTextDidBeginEditingNotification
// UITextViewTextDidChangeNotification
// UITextViewTextDidEndEditingNotification;
UITextField *testTextField;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onActionTextField:)  
                                             name:UITextViewTextDidBeginEditingNotification
                                           object:testTextField];

// 记得移除监听器:[[NSNotificationCenter defaultCenter] removeObserver:self];

3、 代理 <UITextFieldDelegate>

@interface testViewController () <UITextFieldDelegate>
@end

- (void)viewDidLoad {
    UITextField *testTextField;
    testTextField.delegate = self;
}

// 实现代理方法  
- (void)textFieldDidBeginEditing:(UITextField *)textField {
}

//- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField;
//- (void)textFieldDidBeginEditing:(UITextField *)textField;
//- (BOOL)textFieldShouldEndEditing:(UITextField *)textField;
//- (void)textFieldDidEndEditing:(UITextField *)textField;
//- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string;  // 是否要改变文字
//- (BOOL)textFieldShouldClear:(UITextField *)textField;  // clear按钮点击是否有用
//- (BOOL)textFieldShouldReturn:(UITextField *)textField;

在storyboard或者xib中给UIScrollView添加约束:

  1. UIScrollView上下垂直滚动:
    往UIScrollView中添加唯一的UIView子控件,并设置该子控件上下左右的约束都为0;
    设置子控件UIView的高度为固定值(这个值就是UIScrollView的contentSize.height);
    设置子控件UIView水平居中。

  2. UIScrollView左右水平滚动:
    往UIScrollView中添加唯一的UIView子控件,并设置该子控件上下左右的约束都为0;
    设置子控件UIView的宽度为固定值(这个值就是UIScrollView的contentSize.width);
    设置子控件UIView垂直居中。

  3. UIScrollView所有方向滚动:
    往UIScrollView中添加唯一的UIView子控件,并设置该子控件上下左右的约束都为0;
    设置子控件UIView的宽度和高度为固定值;


pod install : 会根据 Podfile.lock 中的版本号来安装第三方库。如果没有 Podfile.lock 文件,则安装最新版本的第三方库。
pod update : 总是安装最新版本的第三方库。


如果在 - viewDidLoad- viewWillAppear: 中延迟设置 tableFooterView 会导致整个UITableView的 contentSize 多出20的高度。
- viewDidAppear: 中直接设置也会导致同样的问题。
因此,如果 tableFooterView 是根据网络请求的数据做UI布局,那么就会出现这种问题。
可以通过 reloadData 方法解决这个问题。

- (void)viewDidLoad {  // - (void)viewWillAppear:(BOOL)animated { }
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        UIView *footerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 1000)];
        footerView.backgroundColor = [UIColor redColor];
        self.testTableView.tableFooterView = footerView;
        [self.testTableView reloadData];
    });
}

沙盒目录:

- Documents  
- Library  
   - Caches  
   - Preferences  
- tmp
// 获取应用程序沙盒的根目录
NSHomeDirectory();

// 获取 tmp 目录
NSTemporaryDirectory();

// 获取 Documents 和 Library目录
[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
[NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];

// 获取 Caches 目录
[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];

获取文件或文件夹的属性 - attributesOfItemAtPath:error:

文件夹属性

NSString *cachesDirectory = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSLog(@"%@", [fileManager attributesOfItemAtPath:cachesDirectory error:nil]);

结果如下:

{
    NSFileCreationDate = "2016-07-25 08:09:12 +0000";  // 文件创建日期
    NSFileExtensionHidden = 0;
    NSFileGroupOwnerAccountID = 20;
    NSFileGroupOwnerAccountName = staff;
    NSFileModificationDate = "2016-08-08 07:38:56 +0000";  // 文件修改日期
    NSFileOwnerAccountID = 501;
    NSFilePosixPermissions = 493;
    NSFileReferenceCount = 5;
    NSFileSize = 170;    // 文件夹本身大小(不包括其内容)
    NSFileSystemFileNumber = 47911067;
    NSFileSystemNumber = 16777220;
    NSFileType = NSFileTypeDirectory;  // 指明这是文件夹类型(目录)
}

文件属性

NSString *cachesDirectory = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *filePath = [cachesDirectory stringByAppendingPathComponent:@"image.jpg"];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSLog(@"%@", [fileManager attributesOfItemAtPath:filePath error:nil]);

结果如下:

{
    NSFileCreationDate = "2016-06-06 09:42:59 +0000";
    NSFileExtendedAttributes =     {
        "com.apple.quarantine" = <30303032 3b353735 35343561 333b5361 66617269 2e617070 3b>;
    };
    NSFileExtensionHidden = 0;
    NSFileGroupOwnerAccountID = 20;
    NSFileGroupOwnerAccountName = staff;
    NSFileModificationDate = "2016-06-06 09:42:59 +0000";
    NSFileOwnerAccountID = 501;
    NSFilePosixPermissions = 420;
    NSFileReferenceCount = 1;
    NSFileSize = 672948;  // 文件大小(字节B)
    NSFileSystemFileNumber = 48891338;
    NSFileSystemNumber = 16777220;
    NSFileType = NSFileTypeRegular;  // 指明这是文件类型
}

- attributesOfItemAtPath:error: 方法返回的是一个字典,可以通过这个字典的【键】来获取对应的【值】。
对于这个字典,系统还提供了 方法 的方式来获取对应的【值】。

NSDictionary *attributes = [fileManager attributesOfItemAtPath:fileDirectory error:nil];

// 获取文件的大小
attributes[NSFileSize];  // 键值对
[attributes fileSize];  // getter方法 也可以通过点语法方式:attributes.fileSize     

// 获取文件类型
attributes[NSFileType];  // 键值对  
[attributes fileType];  // getter方法 也可以通过点语法方式:attributes.fileType

获取文件夹内容的两种方式。

假设App中 NSHomeDirectory()/Library/Caches 目录结构如下:

// 在 /Library/Caches 文件夹下有 AA 和 BB 文件夹以及 201.jpg 图片文件
// 在 AA 文件夹中有 001.jpg、002.jpg 图片文件以及 AB 文件夹
// 在 AB 文件夹中有 101.jpg、102.jpg 图片文件
// 在 BB 文件夹中什么都没有。
- AA
   001.jpg
   002.jpg
   - AB
      101.jpg
      102.jpg
- BB
201.jpg
NSString *cachesDirectory = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *contentsOfDirectory = [fileManager contentsOfDirectoryAtPath:cachesDirectory error:nil];
NSArray *subpaths = [fileManager subpathsAtPath:cachesDirectory];

NSLog(@"%@ \n\n %@", contentsOfDirectory, subpaths);

结果如下:

(
    ".DS_Store",
    "201.jpg",
    AA,
    BB,
)

(
    ".DS_Store",
    "201.jpg",
    AA,
    "AA/.DS_Store",
    "AA/001.jpg",
    "AA/002.jpg",
    "AA/AB",
    "AA/AB/101.jpg",
    "AA/AB/102.jpg",
    BB,
)

可以看出 - contentsOfDirectoryAtPath:error: 只能获取当前目录的下一级文件和文件夹。
- subpathsAtPath: 能获取当前目录下的所有文件和文件夹。

对于 .DS_Store 文件,可以根据具体情况来决定是否需要过滤掉。


SDWebImage下载的图片缓存在 NSHomeDirectory()/Library/Caches/default/com.hackemist.SDWebImageCache.default 目录下。

获取所有通过SDWebImage下载的缓存图片的大小和数量。

#import <SDImageCache.h>

// 大小(字节B)
[[SDImageCache sharedImageCache] getSize];

// 数量
[[SDImageCache sharedImageCache] getDiskCount];

SDWebImage中 - getSize 方法的实现如下(略作修改):

// self.diskCachePath 为 NSHomeDirectory()/Library/Caches/default/com.hackemist.SDWebImageCache.default
- (unsigned long long)getSize {
    unsigned long long size = 0;
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSDirectoryEnumerator *fileEnumerator = [fileManager enumeratorAtPath:self.diskCachePath];
    for (NSString *fileName in fileEnumerator) {
        NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName];
        NSDictionary *attributes = [fileManager attributesOfItemAtPath:filePath error:nil];
        size += [attributes fileSize];
    }
    return size;
}

SDWebImage中 - getDiskCount 方法的实现如下(略作修改):

// self.diskCachePath 为 NSHomeDirectory()/Library/Caches/default/com.hackemist.SDWebImageCache.default
- (NSUInteger)getDiskCount {
    NSUInteger count = 0;
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSDirectoryEnumerator *fileEnumerator = [fileManager enumeratorAtPath:self.diskCachePath];
    count = [[fileEnumerator allObjects] count];
    return count;
}

判断某个文件或文件夹是否存在。

NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL isFileExist = [fileManager fileExistsAtPath:@"/Users/Todd/Desktop/xxx.png"];
BOOL isDirectoryExist = [fileManager fileExistsAtPath:@"/Users/Todd/Desktop"];

总是未完待续。。。