Skip to content

Latest commit

 

History

History
567 lines (365 loc) · 31.9 KB

04.md

File metadata and controls

567 lines (365 loc) · 31.9 KB

让失去焦点的 UIWebView 弹出键盘的方法


作者: 折腾范儿_味精

UIWebView 无法靠代码的方式让失去焦点的 webview 弹出键盘,必须人主动点击 webview,代码 becomefirstresponder 无效。这个问题是可以通过常规合法手段解决,而不是必须操作私有 Api

1 UIWebView 有一个属性叫 keyboardDisplayRequiresUserAction 默认为 YES,就是说只有用户主动操作才可以展现键盘,可以把这行代码置为 NO

2 然后你会发现你调用 becomefirstresponder 依然无效,原因是 iOS Api 依然无法工作,但是 keyboardDisplayRequiresUserAction 关闭,你可以通过 js 来操作 domfocus 事件,从而展现键盘了;

3 [uiwebview stringByEvaluatingJavaScriptFromString:@"document.getElementById('content').focus()"]; 注入一行 js 代码,让 js 对页面中的 ‘content’ 这个 dom,发起 focus 事件,键盘弹出;

4 注意,iOS 客户端同学不要原样照抄这行 js 代码,得根据你的 web 页面的 dom 甚至三方库自行调整 js 的注入代码;

5 简单总结一下:关闭 keyboardDisplayRequiresUserAction ,客户端注入 js,让 js 触发 input 区域的 focus 事件;

使用 Toolchains 在 Xcode 中切换 Swift 版本

作者: 南峰子_老驴

我们每次安装新版本的 Xcode 的时,新 Xcode 都只包含最新版本的 Swift。但通常我们的代码或第三方库还来不及匹配,或者我们想看看新的特性在新旧版本之间的区别,这时候就比较头大了。

苹果爸爸给的解决方案是:multiple Swift toolchains。每个版本的 Swift 都可以单独下载安装。我们可以在 Swift 官方博客上下载:https://swift.org/download/#releases。每个 toolchain 都是一个 .pkg 安装包。我们下载安装完成后,重新启动 Xcode,就可以在 Xcode 的菜单上找到 toolchains 了。

另外,我们也可以在 Preference -> Components: Toolchains 面板里面来管理 Toolchains,如切换、删除等。

需要注意的是,Xcode 自带的默认的 toolchains 不能被移除。

iOS 11 后还使用旧方法获取设备剩余空间?

作者: Lefe_x

iOS11

iOS11 以后使用 NSHomeDirectory() 这种方式获取手机剩余空间总是不准,经测试差不多少 2G 左右,最后查文档后发现 iOS11 后苹果新增了 API。 所以 iOS11 以后需要使用新的 API 才能获取到真实的剩余空间,不过 BYTE 转换为 KB 的时候苹果使用的是 1000,而腾讯视频使用的是 1024

The query type to use depends on what's being stored. If you’re storing data based on a user request or resources the app requires to function properly (for example, a video the user is about to watch or resources that are needed for the next level in a game), query against NSURLVolumeAvailableCapacityForImportantUsageKey. However, if you’re downloading data in a more predictive manner (for example, downloading a newly available episode of a TV series that the user has been watching recently), query against NSURLVolumeAvailableCapacityForOpportunisticUsageKey.

  • NSURLVolumeTotalCapacityKey 获取到整个手机的存储空间,比如32G的手机获取的数据是32G;

  • NSURLVolumeAvailableCapacityKey 可用容量;

  • NSURLVolumeAvailableCapacityForImportantUsageKey 苹果建议如果下载视频或者游戏下一关的数据用这个,经调查发现系统中的剩余空间和腾讯视频使用的都是这个;

  • NSURLVolumeAvailableCapacityForOpportunisticUsageKey苹果建议如果您以更具预见性的方式下载数据(例如,下载最近使用的一个电视系列节目,用户最近一直在观看)。

iOS11以前

iOS11 以前获取的手机剩余空间总是多余系统中的剩余空间。我手机系统是iOS9.3.1 获取到的剩余空间差不多为 7.98G,而系统显示为 7.8G,比系统获取的还要多,而且不确定苹果使用的是 1000 还是 1024 进行转换,如果使用 1000 转换的话,那么我获取到的剩余空间会比系统显示的剩余空间还要大。如果我用 1000 转换,大约为 8.56GB

参考

Checking Volume Storage Capacity

webview关闭时手动停止音频播放

作者: halohily

当我们使用 webview 展示网页时,页面内若含有音频标签,点击播放,这时关闭带有 webview 的 VC,会发现即使 webview 已经被释放,音频还是没有停止。这时可以采用比较快捷的方法来做到 webview 被关闭时停止正在播放的音频:webview 重新 load 页面,或者执行停止音频播放的 JavaScript 语句。

这里以 UIWebview 举例,WKWebview 同理。

方法一:重新 load 一个空白页面

[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"about:blank"]]];

方法二:手动执行停止音频的 JavaScript 语句

[self.webView stringByEvaluatingJavaScriptFromString:@"audioPause()"];

当然,这两种方法都是比较简便但不优雅的实现方式,适合轻度使用 webview 的场景。如果你们对于 webview 做了比较多的加工,是可以监听 webview 中的音频、视频任务,来手动停止的。

关于 Xcode console 输出的 UIImage 警告的解决方式

作者: Vong_HUST

最近项目中遇到 Xcode console 偶尔输出 [framework] CUICatalog: Invalid asset name supplied: '(null)' 这样一段 warning,毫无头绪。在爆栈上看到有人遇到一样的问题,原因是由于 [UIImage imageNamed:] 传了 nil 或者传入的 stringlength 为0。至于怎么找到具体是哪里传了 nil,可以打一个全局断点,然后加一个条件来判断入参是否为空,即可找到有问题的地方。如图所示

还有遇到 Could not load the "some-image-name" image referenced from a nib in the bundle with identifier "com.xxxx" 这种情况,一般情况下是这张图片被删除了,但是 Xib/Storyboard 中还引用了这张图片,表现形式主要是 UIImageViewimage 属性一栏是 Unknown,如下图

只要把这个 Unknown 改成对应图片即可。

参考链接: Error: CUICatalog: Invalid asset name supplied Could not load the “xxx” image referenced from a nib in the bundle with identifier

iOS9之后字符串变换API

作者: 高老师很忙

今天给大家介绍的是 iOS9 之后 NSString 新增的方法: - (nullable NSString *)stringByApplyingTransform:(NSStringTransform)transform reverse:(BOOL)reverse ,我用了它的翻译功能之后,喜欢称之为翻译方法,说是翻译可能不太准确,语言变换会更准确一些。

iOS 实现了部分 ICU(International Components for Unicode的缩写,主要提供提供了 Unicode 和全球化支持)功能,所以才有了这个方法的诞生。因此除了系统提供的转换方式(如下图)之外,还支持ICU语法。

ICU 转换语法比如说,简体转繁体(Hans-Hant),繁体转简体(Hant-Hans),字母过滤([:^Letter:] Remove),还有转换成小写(Lower)等等。

我做了一个简单的Demo,核心代码如下:

是不是发现项目里汉语转拼音的三方库也可以干掉了? 😜比较遗憾的是ICU是不支持英文翻译的,所以这个API 也是不支持的。只支持简体,繁体,日语,韩语等多语言的APP用这个方法还是比较方便的,在处理字符串上面也是比较不错,去掉emoji也可以一行代码搞定。

ICU相关内容详看:http://userguide.icu-project.org/transforms/general/rules

如何优雅地获取 ScrollView 的滚动方向

作者: KANGZUBIN

在有些场景,我们可能需要获取 UIScrollView(及其子类)的滚动方向来做不同的操作。

我们首先能想到最直观的方法是:用一个变量或属性 lastContentOffset 去保存 scrollView 上次的 content offset 值,然后在 UIScrollViewscrollViewDidScroll: delegate 方法中跟 scrollView 当前实时的 content offset 做对比来判断滚动方向,代码大致如下:

// @property (nonatomic, assign) CGFloat lastContentOffset;

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    if (self.lastContentOffset > scrollView.contentOffset.y) {
        // 向下滚动
    } else if (self.lastContentOffset < scrollView.contentOffset.y) {
        // 向上滚动
    }
    self.lastContentOffset = scrollView.contentOffset.y;
}

今天我们同事在阅读第三方开源代码时,看到一个更简便的方法,同样在 scrollViewDidScroll: 方法中,先获取 scrollView 的 panGestureRecognizer(拖拽/移动动作)手势,然后把手势滑动的相对偏移在当前 view 上转换成一个 point,最后根据 point 的 x 或 y 来判断左右/上下滚动方向,代码如下:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    CGPoint point = [scrollView.panGestureRecognizer translationInView:self.view];
    if (point.y > 0) {
        // 向下滚动
    } else {
        // 向上滚动
    }
}

虽然这种方式看似很优雅,可以不用借助额外的变量来完成,但它存在一个问题,如下图所示,我们手指按住屏幕不放,先向上滑动一段距离(从 A -> B,向上)然后改变滑动方向再向下滑动一段距离,(从 B -> A -> C,向下):

但这时候通过第二种方法判断实际得到的结果是:A -> B 向上滚动,B -> A 向上滚动,A -> C 向下滚动,显然,其中 B -> A 的方向判断是错的,应该是向下。

原因在于,上述方法中拖拽手势的相对偏移 point 是根据滑动的起始点 A 来进行计算的,所在只要手势停留在起始点 A 之上,不管向上还是向下滑动,它都认为是向上滚动了。

所以这种优雅的方法只适用于 scrollView 一次手势滑动中不改变方向的情况。

参考连接:Finding the direction of scrolling in a UIScrollView?

iOS 你的APP中能藏的住秘密吗?

作者: Lefe_x

今天知识小集以不一样的方式给大家推送一条“小”集。经过不断的打磨,为知识小集提供的微信截图工具已开源,有需要的小伙伴可以点击原文下载,还没关注知识小集的公众号,点原文关注哈。本文主要向大家普及一些逆向的知识,以防止别人破解你的APP,拿到重要数据。其实,APP中没有什么安全可言,别人都会轻而易举拿到APP中的数据,修改代码的执行逻辑等。昨天转发了 @没故事的桌同学 的一条微博,有很多同学咨询说使用 Reveal 必须使用越狱机吗?当然不用也可以。文章会有提到,查看原理

我们可以利用 NSURLProtocol 做什么

作者: halohily

今天和大家聊一聊 NSURLProtocol,可能你已经听说过它。因篇幅有限,这里不讲解具体的使用方式,主要是和大家聊一聊我们可以利用 NSURLProtocol 做的一系列事情,权当抛砖引玉,大家可以根据自己的需要去深入了解。

简单来说 NSURLProtocol 是苹果 URL Loading System 中的一个抽象类。通过实现其子类并注册到 app 中,我们可以拦截 app 中的网络请求。那么拦截网络请求可以做什么呢?这里举几个小例子:

  • app 内置了测试服、正式服的切换开关,需要通过开关一键切换所有网络请求使用的 server 地址。
  • 项目内部分模块使用了 ReactNative,需要动态配置由前端发出的网络请求 server 地址(特殊在于虽然是前端发出,但并非来自 webview )。这个例子和例 1 类似。
  • 项目内含有 hybrid 模块。对 hybrid 页面加速的一个策略,可以是客户端在合适时机提前缓存资源文件,并且以与 webview 加载资源的路径相对应的方式存储在本地。这样当 webview 加载资源时,通过拦截判断是否是本地已经存在的资源的请求,如果是,则使用本地资源构造 response 。这样可以显著提升被过多资源请求影响的加载速度。
  • 限制 appwebview 的跳转行为,例如禁止向某域名下的跳转,或者对于某个域名下的跳转做重定向操作。当然这些使用 webview 的代理方法也可以做到。
  • 对于 app 内发出的所有网络请求,需要添加公共的 header 内容。
  • 需要统计 app 内各处对某个 api 的调用次数等数据。
  • 需要统计 app 内的网络请求失败率。

参考链接:

CoreData 检索遇到的坑及其解决方式

作者: Vong_HUST

项目中有用到 CoreData 的同学应该对 MagicalRecord 这个库或多或少有一点了解,我们项目中也用到这个库的搜索功能即 NSManagedObject (MagicalFinders) 这个分类。

最近遇到一个问题就是两个 CoreDataModelFatherSonSon 继承自 Father。在 Father 执行 MR_findxxx 等一系列方法时,会把 Son 的实例也找出来。一番搜索下来发现有人在 MagicalRecord 提了个类似的 issue。然后发现 NSFetchRequest 有一个 includesSubentities 属性,直接将其设置成 NO,即可。代码如下

+ (NSArray *)findAllOrderBy:(NSString *)orderItem ascending:(BOOL)ascending inContext:(NSManagedObjectContext *)context {
    NSFetchRequest *request = [self requestAllInContext:context];
    [request setIncludesSubentities:NO];
    [request setFetchBatchSize:[self defaultBatchSize]];
    NSSortDescriptor *sortBy = [[NSSortDescriptor alloc] initWithKey:orderItem ascending:ascending];
    [request setSortDescriptors:[NSArray arrayWithObject:sortBy]];
    
    return [self executeFetchRequest:request inContext:context];
}

参考链接:

mr_fetchAllSorted fetches not only the parent entity but also the child entity

NSPredicate that filters out subclass results

为icon换装

作者: 高老师很忙

最近一直在研究怎么减少资源文件,如果产品童鞋要用不同颜色的图片展示不同的状态,状态 A 是绿色的,状态 B 是红色的,状态 C 是黄色,图案都是一样的,只是颜色不同,也许你会让设计童鞋切3张图,但这种方式不太优雅。

有两种方案:一个是使用 tintColor,可以简单的实现更换颜色;如果这个方法不能满足需求,可以使用绘制的方式,可选参数会多很多。喵神之前发表过一篇文章——**《iOS中使用blend改变图片颜色》**很是受用,虽然发表时间有点早,但我个人觉得还是比较实用。

我们可以使用 UIImage-drawInRect:blendMode:alpha: 方法,封装后一行代码搞定你想要的任何颜色,直接上代码:

运行效果如图:

kCGBlendModeDestinationIn 是保留透明度信息;但如果图片中有阴影就需要与 kCGBlendModeOverlay 或者 kCGBlendModeMultiply 或其它参数根据实际情况叠加使用。

喵神已经把原理讲解的很详细了,感兴趣的童鞋可以移步:https://onevcat.com/2013/04/using-blending-in-ios/

使用 LLDB bugreport 命令导出 App 运行崩溃日志

作者: KANGZUBIN

在日常开发调试 App 过程中,当我们写的代码有 Bug 导致崩溃时,此时我们通常会断点到崩溃的位置,然后查看 Xcode 控制台输出崩溃原因进行解决。

但有些时候我们手头可能有其它的活不能立即进行排查,或者崩溃的是其他同事的代码,需要先把控制台的崩溃日志复制粘贴到其他地方保存起来,过后再看或者告知同事进行解决。强大的 LLDB 调试工具提供了一个 bugreport 命令帮我们快速完成导出日志这件事。

例如,有一段数组越界崩溃的代码如下:

- (void)testBugReport {
    NSArray *testArray = @[@"1", @"2", @"3"];
    NSLog(@"%@", testArray[4]);
}

此时在控制台执行如下命令:

bugreport unwind --outfile /Users/kangzubin/Desktop/buglog.txt

它可以生成一份当前 App 运行状态的完整报告,包含崩溃的调用栈信息,大致如下:

另外,我们可以在上述命令后面加一个 --append-outfile 修饰符,用于在已有的日志文件中追加新的崩溃日志信息,而不是覆盖。

bugreport unwind --outfile <path to output file> --append-outfile

参考:Debugging Swift code with LLDB

Xcode 9 中快速定位目标控制器

作者: moon2light

你有没有这种困扰,接手一个老项目或项目目录结构复杂的时候,我们要修改某个控制器的界面/功能时,控制器的定位就有些许困难。苹果爸爸很早就意识到这个问题了,也一直在优化这里,今天就分享一下该 UI 调试小技巧。

充分条件:

  1. Xcode 9.0 及以上
  2. 模拟器/真机 iOS 11.0 及以上

过程:

1.模拟器/真机定位需要调试的界面.

2.定位到 debug 工具 -> 点击 debug view hierarchies

3.轻转一下视图层次,就可以看到整个的页面的层次结构,今天的重点在这里,控制器实例。

4.点击控制器实例(上图红框部分),看 Xcode 右侧 show the object inspector

5.当当当~是不是感觉到类名很熟悉,点击右侧小箭头,就会跳转到对应控制器的 .h 文件。

6.command+shift+j 文件导航栏中定位文件,找到 .m 文件, enjoy debugging

讨论一个常见的需求实现

作者: Lefe_x

今天 Lefe_x 打算和大家讨论一个非常常见的需求。如果是你,你会咋么做?你会不会使用一种更优雅的方式?

PM:我们要实现一个下载音频的功能,下载前需要做这些处理。

  • 如果网络异常,直接提示网络异常并退出下载;
  • 如果是 4G 网络,需要弹窗提醒用户选择是否继续下载,用户点击下载后进入下载流程,取消后直接退出下载;
  • 如果 wifi 直接进入下载;
  • 下载时需要获去下载地址,获取到下载地址后,进入下载;
  • 下载完成需要刷新 UI;

梳理完逻辑后是这样的:

:这个好办,不一会就写完了,结果是这样的。

- (void)downloadCallBack:(void(^)(BOOL isSuccess))callback
{
    if ([self isBadNetwork]) {
        callback(NO);
        return;
    }
    
    if ([self is4GNetwork]) {
        [self showAlert:^(NSInteger index) {
            if (index == 1) {
                [self requestDownloadUrl:^(NSString *url) {
                    [self startDownloadWithURLString:url callback:^(BOOL isSuccess) {
                        callback(YES);
                    }];
                }];
            } else {
                callback(NO);
            }
        }];
    } else {
        [self requestDownloadUrl:^(NSString *url) {
            [self startDownloadWithURLString:url callback:^(BOOL isSuccess) {
                callback(YES);
            }];
        }];
    }
}

上面这种写法主要有回调地狱和很多冗余代码,解决这种问题,我们可以使用 Promise 这种异步编程的方式来避免这些问题。如下:

- (void)downloadForPromiseCallBack:(void(^)(BOOL isSuccess))callback
{
    // 检查是否可以下载
    AnyPromise *checkPromise = [self checkCanDownloadPromise];
    checkPromise.then(^(){
        // 请求下载地址
        return [self requestUrlPromise];
    }).then(^(NSString *downloadUrl){
        // 开始下载音频
        return [self downloadPromiseWithURLString:downloadUrl];
    }).then(^(){
        // 下载成功
        if (callback) {
            callback(YES);
        }
    }).catch(^(NSError *error){
        // 所有的异常
        callback(NO);
    });
}

如果你有好的想法,不防提出来,我们一起讨论哈。

参考:

primise 的使用 https://github.com/lefex/LefexWork/blob/master/blog/iOS/Promise.md

说说 NSTimer 的新 API

作者: halohily

在以往的 iOS 版本中,我们为了避免 NSTimer 的循环引用问题,一个比较常用的解决办法是为 NSTimer 添加一个 category,新增传入 block 类型参数的接口。分类内部实现是将此 block 作为 NSTimer 的 userInfo 参数传入,而 NSTimer的 target 则设置为 timer 自己。以此来避免 NSTimer 持有 VC。代码如下:

// NSTimer+BlocksSupport.h
#import <Foundation/Foundation.h>

@interface NSTimer (BlocksSupport)
+ (NSTimer *)ly_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
repeats:(BOOL)repeats
block:(void(^)())block;
@end

// NSTimer+BlocksSupport.m
#import "NSTimer+BlocksSupport.h"

@implementation NSTimer (BlocksSupport)
+ (NSTimer *)ly_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
repeats:(BOOL)repeats
block:(void(^)())block;
{
	return [self scheduledTimerWithTimeInterval:interval
						target:self
					  selector:@selector(ly_blockInvoke:)
					  userInfo:[block copy]
					   repeats:repeats];
}

+ (void)ly_blockInvoke:(NSTimer *)timer {
	void (^block)() = timer.userInfo;
	if(block) {
		block();
	}
}
@end

而在 iOS 10 之后,苹果终于为 NSTimer 添加了一个官方 API,支持传入 block 类型参数。可谓是千呼万唤始出来。新官方 API 包括:

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval
repeats:(BOOL)repeats
block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
repeats:(BOOL)repeats
block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

模块化带来的痛之模块之间的数据同步

作者: Lefe_x

如今,模块化已经屡见不鲜,目前很多公司都在做。然而在使用模块化的过程中,各个模块之间是如何通信的?我们一起先看下具体的需求:

PM:这一版本做一个下载功能,包含我的下载,待下载,正在下载等,下载入口会在A,B,C模块中都会显示,而且需要把下载状态和进度实时显示到 UI 上。

我:从需求出发,需要设计一个下载模块。但是,A,B,C 模块都需要实时了解当前的下载状态和进度以展示到 UI,那么问题来了如何做到下载模块与 A,B,C 模块之间进行下载状态和进度同步呢?

模块之间数据同步可以有以下几种方式:

  • 1.通知 通知是最简单的方式,每当下载状态和进度变化的时候,需要发通知出去,这样其它模块即可接收到当前的变化。他的缺点是通知太分散,不太好控制。

  • 2.注册监听 下载队列是一个单例,可以直接通过路由的方式注册监听,当下载状态和进度变化的时候给注册者发送消息。

如果你有什么好的方式,欢迎讨论,这条小集只是起到了抛砖引玉的作用,希望大家可以把更好的方案分享出来。

CoreData 关系的4种删除规则

作者: Vong_HUST

由于项目是基于 CoreData 的,所以 CoreData 方面遇到的问题比较多。今天主要为大家分享一下 CoreData 中关系的4种删除规则。

先假设我们有两个实体,BlogCategory,一篇 Blog 只能属于一个 Category,一个 Category 可以有多篇 Blog。 如图:

1.No Action

规则为 No Action 时,当 category 删除时, blogs 是不会被通知到的,由于 CoreData 里关系是双向的,blog 这边依然认为他被关联到对应的 category。这种规则到目前还没用到过,也没有具体的使用场景,如果设置成 No Action,如果操作不当,可能会有崩溃发生

2.Nullify

还是拿上面举例,如果 category 被删除,blog 对应的 category 关系会被置为 nil。这个是系统默认删除规则,也是日常中用的最多的删除规则。

3.Cascade

Cascade 规则某些场景下也十分有用。还是用上面例子,一般情况下,我们想 category 被删除之后,其拥有的所有 blog 实例也要删除,那这个时候直接将删除规则设置成这个即可。CoreDatacategory 删除后会自动将其关联的 blog 也全部删除。但这种情况一般只存在与一对多(或一对一)的情况,如果是多对多,就不适合用这种规则。

4.Deny

这个规则刚好和 Cascade 相反,category 只有在其所拥有的 blogs 都被删除的情况下才会被删除。这种情况在我们项目中,也没有具体使用场景。

综上,一般业务场景下 NullifyCascade 规则已经可以满足。如果有其他特殊场景也可以考虑1和4。欢迎补充和讨论~

Objective-C 可变容器对象的初始化方法使用总结

作者: KANGZUBIN

最近在 Review Code 时,发现团队中不同成员对一个 可变字典空对象 的初始化方式写法都不太一致,主要有以下几种:

// 第 1 种
NSMutableDictionary *dict1 = [[NSMutableDictionary alloc] init];

// 第 2 种
NSMutableDictionary *dict2 = [NSMutableDictionary new];

// 第 3 种
NSMutableDictionary *dict3 = [NSMutableDictionary dictionary];

// 第 4 种
NSMutableDictionary *dict4 = [NSMutableDictionary dictionaryWithCapacity:10];

// 第 5 种
NSMutableDictionary *dict5 = @{}.mutableCopy;

我们知道在 Objective-C 中主要有三大容器,分别是数组、字典、集合,它们各自都对应有可变对象和不可变对象,如:NSArray/NSMutableArray, NSDictionary/NSMutableDictionary, NSSet/NSMutableSet, 我们这里不再赘述它们的区别和使用方式,下面主要以 NSMutableDictionary 为例介绍以上几种初始化写法的不同。

第 1 种就是我们常见初始化一个 NSObject 对象的写法,其中 allocNSObject 的类方法,它用于创建(分配内存)并返回指定类一个的新对象,而 initNSObject 的实例方法,一般由子类重新实现,用于初始化一个刚创建 (allocated) 的对象。

第 2 种写法,对于 NSObjectnew 方法,苹果文档 是这么说的:Allocates a new instance of the receiving class, sends it an init message, and returns the initialized object. 因此,它就是 allocinit 方法的组合,与第 1 种写法是等价的。

第 3 种,文档描述:Creates and returns an empty dictionary. 它也是一种快速的初始化写法。在 ARC 下,它与 [[NSMutableDictionary alloc] init] 是相同的;但在 MRC 手动管理内存时,使用 [[NSMutableDictionary alloc] init] 创建并初始化对象,后续我们需要手动调用 release 方法释放,而 [NSMutableDictionary dictionary] 相当于 [[[NSMutableDictionary alloc] init] autorelease],区别在于你不用再调用 release 方法去释放它了。

第 4 种,相当于调用 [[NSMutableDictionary alloc] initWithCapacity:10] 方法,它用于创建一个可变字典对象并初始化分配给它足够的内存空间以存储指定长度(10)个内容对象,且当动态添加的数据超过初始化时指定的长度,也会自动增加分配新的内存,所以如果你可以确定要用的可变字典大致的存储个数,推荐使用这种方式。

对于第 5 种,我们知道 @{} 字面值相当于创建了一个不可变的 NSDictionary 空对象,然后调 NSObject 的 mutableCopy 拷贝成一个新的可变对象赋给 dict5

此外,对于 NSMutableArrayNSMutableSet 也有与上述类似的几种不同的初始化写法,不再一一分析。

以上是对可变字典空对象的几种不同初始化写法的简单对比,你习惯用哪一种呢?欢迎留言讨论...

参考链接1参考链接2

水平或者竖直布局方案

作者: 高老师很忙

如果想实现上图这种水平布局,大家会采取哪些技术方案呢?一些常规操作我就不介绍了,我主要是介绍2种比较方便的解决方案,可以适用于固定个数的子视图和不确定个数的子试图(eg:根据接口返回显示子视图的个数)。

首先推荐iOS9之后推出的UIStackView,使用比较方便,嵌套使用也比较方便,如果是在xib或者Storyboard直接拖几个控件,设置一下属性就可以实现上面的效果,如果手动Coding的话也很简单,如图:

另一个方案是Masonry库封装的方法,这种布局也可以轻松实现,如图:

如果有其他更简单的方案,欢迎一起分享!