《Effective Objective-C 2.0 编写高质量 iOS 与 OS X 代码的 52 个有效方法 》
一、熟悉 Objectice-C

// ===============================================================
// 异步后台执行
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// do something
});
// ===============================================================
// 异步主线程执行
dispatch_async(dispatch_get_main_queue(), ^{
// do something
});
// ===============================================================
// 一次性执行:单例
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// code to be executed once
});
// ===============================================================
// 延迟2秒执行
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^{
// code to be executed on the main queue after delay
});
// ===============================================================
// 使用 dispatch_queue_create() 自定义 dispatch_queue_t,
// 第一个参数是反向DNS样式命名惯例;
// 第二个参数指定你的队列是串行还是并发;
// 注意:当你在网上搜索例子时,你会经常看人们传递 0 或者 NULL 给 dispatch_queue_create 的第二个参数。这是一个创建串行队列的过时方式;明确你的参数总是更好。
dispatch_queue_t urls_quene = dispatch_queue_create("com.selander.GooglyPuff.photoQueue", NULL);
dispatch_async(urls_quene, ^{
// your code
});
// ===============================================================
// 并行执行线程
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
// 并行执行的线程一
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
// 并行执行的线程二
});
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
// 汇总结果
});
// ===============================================================
// 每秒执行
-(void)startTime{
__block int timeout = 60; // 倒计时时间
NSTimeInterval intervalInSeconds = 1.0; // 执行时间间隔
//获取后台队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 以下代码可以使用代码片段库: dispatch_source_t timer
// 创建一个timer放到队列中
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 设置timer的首次执行时间、执行时间间隔、精确度
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, intervalInSeconds * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
// 设置timer执行的事件
if (timeout <= 0) {
dispatch_source_cancel(timer); // 关闭timer
dispatch_async(dispatch_get_main_queue(), ^{
// 倒计时结束,回到主线程更新UI
});
}else {
dispatch_async(dispatch_get_main_queue(), ^{
// 倒计时进行中,更新UI
NSLog(@"===%ds===",timeout);
});
}
timeout --;
});
// 激活timer
dispatch_resume(timer);
}
* - DISPATCH_QUEUE_PRIORITY_HIGH: QOS_CLASS_USER_INITIATED
* - DISPATCH_QUEUE_PRIORITY_DEFAULT: QOS_CLASS_DEFAULT
* - DISPATCH_QUEUE_PRIORITY_LOW: QOS_CLASS_UTILITY
* - DISPATCH_QUEUE_PRIORITY_BACKGROUND: QOS_CLASS_BACKGROUND
下面是一个关于在 dispatch_async 上如何以及何时使用不同的队列类型的快速指导:
dispatch_sync。dispatch_async 到主队列,你能确保这个新任务将在当前方法完成后的某个时间执行。不知道何时适合使用 dispatch_after ?
dispatch_after 要小心。你最好坚持使用主队列。dispatch_after 的好选择;Xcode 提供了一个不错的自动完成模版。dispatch_after 也要小心;你会这样做就比较罕见。还是在主队列做这些操作吧。下面是你何时会 —— 和不会 —— 使用障碍函数 Dispatch barriers 的情况:
下面是一个快速总览,关于在何时以及何处使用 dispatch_sync :
dispatch_sync 放在同一个队列,那你就百分百地创建了一个死锁。关于何时以及怎样使用有着不同的队列类型的 Dispatch Group :
dispatch_group_wait- (void)downloadPhotosWithCompletionBlock:(BatchPhotoDownloadingCompletionBlock)completionBlock
{
// 因为你在使用的是同步的 dispatch_group_wait ,它会阻塞当前线程,所以你要用 dispatch_async 将整个方法放入后台队列以避免阻塞主线程。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ // 1
__block NSError *error;
// 创建一个新的 Dispatch Group,它的作用就像一个用于未完成任务的计数器。
dispatch_group_t downloadGroup = dispatch_group_create(); // 2
for (NSInteger i = 0; i < 3; i++) {
NSURL *url;
switch (i) {
case 0:
url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString];
break;
case 1:
url = [NSURL URLWithString:kSuccessKidURLString];
break;
case 2:
url = [NSURL URLWithString:kLotsOfFacesURLString];
break;
default:
break;
}
// dispatch_group_enter 手动通知 Dispatch Group 任务已经开始。你必须保证 dispatch_group_enter 和 dispatch_group_leave 成对出现,否则你可能会遇到诡异的崩溃问题。
dispatch_group_enter(downloadGroup); // 3
Photo *photo = [[Photo alloc] initwithURL:url
withCompletionBlock:^(UIImage *image, NSError *_error) {
if (_error) {
error = _error;
}
// 手动通知 Group 它的工作已经完成。再次说明,你必须要确保进入 Group 的次数和离开 Group 的次数相等。
dispatch_group_leave(downloadGroup); // 4
}];
[[PhotoManager sharedManager] addPhoto:photo];
}
// dispatch_group_wait 会一直等待,直到任务全部完成或者超时。如果在所有任务完成前超时了,该函数会返回一个非零值。你可以对此返回值做条件判断以确定是否超出等待周期;然而,你在这里用 DISPATCH_TIME_FOREVER 让它永远等待。它的意思,勿庸置疑就是,永-远-等-待!这样很好,因为图片的创建工作总是会完成的。
dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER); // 5
// 此时此刻,你已经确保了,要么所有的图片任务都已完成,要么发生了超时。然后,你在主线程上运行 completionBlock 回调。这会将工作放到主线程上,并在稍后执行。
dispatch_async(dispatch_get_main_queue(), ^{ // 6
// 最后,检查 completionBlock 是否为 nil,如果不是,那就运行它。
if (completionBlock) { // 7
completionBlock(error);
}
});
});
}
dispatch_group_notify- (void)downloadPhotosWithCompletionBlock:(BatchPhotoDownloadingCompletionBlock)completionBlock
{
// 1
// 在新的实现里,因为你没有阻塞主线程,所以你并不需要将方法包裹在 async 调用中。
__block NSError *error;
dispatch_group_t downloadGroup = dispatch_group_create();
for (NSInteger i = 0; i < 3; i++) {
NSURL *url;
switch (i) {
case 0:
url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString];
break;
case 1:
url = [NSURL URLWithString:kSuccessKidURLString];
break;
case 2:
url = [NSURL URLWithString:kLotsOfFacesURLString];
break;
default:
break;
}
// 同样的 enter 方法,没做任何修改。
dispatch_group_enter(downloadGroup); // 2
Photo *photo = [[Photo alloc] initwithURL:url
withCompletionBlock:^(UIImage *image, NSError *_error) {
if (_error) {
error = _error;
}
// 同样的 leave 方法,也没做任何修改。
dispatch_group_leave(downloadGroup); // 3
}];
[[PhotoManager sharedManager] addPhoto:photo];
}
// dispatch_group_notify 以异步的方式工作。当 Dispatch Group 中没有任何任务时,它就会执行其代码,那么 completionBlock 便会运行。你还指定了运行 completionBlock 的队列,此处,主队列就是你所需要的。
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{ // 4
if (completionBlock) {
completionBlock(error);
}
});
}
那何时才适合用 dispatch_apply 呢?
dispatch_apply 的功能;你还不如直接使用普通的 for 循环。dispatch_apply 。还是用普通的 for 循环吧。- (void)downloadPhotosWithCompletionBlock:(BatchPhotoDownloadingCompletionBlock)completionBlock
{
__block NSError *error;
dispatch_group_t downloadGroup = dispatch_group_create();
dispatch_apply(3, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(size_t i) {
NSURL *url;
switch (i) {
case 0:
url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString];
break;
case 1:
url = [NSURL URLWithString:kSuccessKidURLString];
break;
case 2:
url = [NSURL URLWithString:kLotsOfFacesURLString];
break;
default:
break;
}
dispatch_group_enter(downloadGroup);
Photo *photo = [[Photo alloc] initwithURL:url
withCompletionBlock:^(UIImage *image, NSError *_error) {
if (_error) {
error = _error;
}
dispatch_group_leave(downloadGroup);
}];
[[PhotoManager sharedManager] addPhoto:photo];
});
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
if (completionBlock) {
completionBlock(error);
}
});
}
typedef enum UIModalPresentationStyle : NSInteger {
UIModalPresentationFullScreen = 0,
// UIModalPresentationFullScreen 代表弹出VC时,presented VC充满全屏,如果弹出VC的wantsFullScreenLayout 属性设置为YES的,则会填充到状态栏下边,否则不会填充到状态栏之下。
UIModalPresentationPageSheet,
// UIModalPresentationPageSheet 代表弹出VC时,presented VC的高度和当前屏幕高度相同,宽度和竖屏模式下屏幕宽度相同,剩余未覆盖区域将会变暗并阻止用户点击,这种弹出模式下,竖屏时跟 UIModalPresentationFullScreen 的效果一样,横屏时候两边则会留下变暗的区域。
UIModalPresentationFormSheet,
// UIModalPresentationFormSheet 这种模式下,presented VC的高度和宽度均会小于屏幕尺寸,presented VC居中显示,四周留下变暗区域。
UIModalPresentationCurrentContext,
// UIModalPresentationCurrentContext 这种模式下,presented VC 的弹出方式和 presenting VC的父VC的方式相同。
UIModalPresentationCustom,
// 自定义视图展示风格,由一个自定义演示控制器和一个或多个自定义动画对象组成。符合UIViewControllerTransitioningDelegate 协议。使用视图控制器的 transitioningDelegate设定您的自定义转换。
UIModalPresentationOverFullScreen,
// 如果视图没有被填满,底层视图可以透过
UIModalPresentationOverCurrentContext,
// 视图全部被透过
UIModalPresentationPopover,
// 弹窗样式
UIModalPresentationNone = -1
} UIModalPresentationStyle;
⭐️⭐️⭐️以下内容来源于官方源码、 README 文档、测试 Demo 或个人使用总结 !
UIPopoverPresentationController 对象用于管理弹窗(popover)内容的显示。 从一个弹窗被呈现到被 dismiss 过程中,UIKit 使用这个类的实例来管理 presentation 行为。 您可以使用此类的实例来配置( presentation 样式设置为 UIModalPresentationPopover 的)视图控制器的弹出式外观和行为。
几乎所有的情况下,你直接使用这个类,而不是创建它的实例。当你使用 UIModalPresentationPopover 样式来呈现视图控制器时,UIKIt 会自动创建该类的实例。你可以从显示的视图控制器的 popoverPresentationController 属性中检索该实例,并使用它来配置 popover 行为。
如果你不想在呈现视图控制器后立即配置弹出窗口,则可以使用委托对象来配置弹出窗口。 在演示过程中,popover 演示控制器调用其委托的各种方法 - 遵守 UIPopoverPresentationControllerDelegate 协议的对象 - 请求并通知它获取关于演示的状态信息。 您的委托对象可以使用这些方法配置 popover 并根据需要调整其行为。 有关如何为 Popover 演示控制器实施委托的信息,请参阅 UIPopoverPresentationControllerDelegate。
// UIimage 对象
UIImage *image = [UIImage imageNamed:@"7th.png"];
// 创建 NSData 对象
// UIImageJPEGRepresentation 函数:以JPEG格式返回指定图像的数据。
// 参数一:image 对象
// 参数二:compressionQuality 图像压缩率(0.0~1.0)
NSData *imageData = UIImageJPEGRepresentation(image, 1.0);
// 1.UIImage ——> base64 String
NSString *encodedString = [imageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
// 2.UIImage ——> base64 Data
NSData *encodedData = [imageData base64EncodedDataWithOptions:NSDataBase64Encoding64CharacterLineLength];
NSLog(@"encodedString:%@",encodedString);
NSLog(@"encodedData:%@",encodedData);