licc

心有猛虎 细嗅蔷薇

0%

最近项目上线了Vip订阅功能,包含连续包月和连续包年。上线后市场和运营同事问能不能给一些资深用户送几个月的会员,或者搞活动送会员。因为是海外项目,内购不需要用户系统(我们也没有用户系统)完全依赖Apple购买凭证。所以不能使用国内App那种服务端生成兑换码的做法。

因为完全依赖Apple,所以还是要从Apple这边下手。自然而然想到了Apple的促销代码。
先看看什么是促销代码 官方文档戳这里

可以看到,促销代码是可以兑换内购项目的,并且订购一个自动续期的订阅并不会自动续费。

Read more »

目前负责的App新增了Widget功能,之后在组内分享中分享了下Widget的开发经验。基于之前的PPT提炼出了这篇文章。本篇文章只讲基于Widget关于iOS10+ 之后的知识点。

Widget是iOS8以后Apple推出的一项功能,并且在iOS10后进行了大幅的优化。

在主屏幕和锁定屏幕上向右滑动来访问Widget,也可以在对应的App图标上面使用3D Touch按压访问相应的Widget。

Read more »


assert大家都不陌生,在开发阶段用断言函数可以很好的进行调试。

一般OC中,我们使用的是NSAssert。在Xcode4.2以后,Xcode会在release环境下自动把NSAssert忽略掉,确保release环境不会出错。

assert的坑

最近遇到一个问题,线上Crash定位在了一个pod库中的assert方法,在OC里面调用assert是用的C语言的assert。结果在release环境下也生效了,直接造成了Crash。

查找了一些资料,找到了解决方案。

通过查询源码发现,在C语言中是如何忽略的assert

#undef assert
#undef __assert

#ifdef NDEBUG
#define assert(e) ((void)0)
#else

#ifndef __GNUC__

源码地址详见:https://opensource.apple.com/source/Libc/Libc-583/include/assert.h.auto.html

NDEBUG是C语言中的一个标准宏,其语义适用于C89,C99,C ++ 98,C ++ 2003,C ++ 2011,C ++ 2014标准。
详见:https://stackoverflow.com/questions/2290509/debug-vs-ndebug

根据源码可知,如果定义了NDEBUG,那么assert就返回void,就不会再生效。

那么我们只需要在主工程中,选择Bulid Settings选择Preprocessor MarcosRelease中添加NDEBUG=1即可。

但是需要注意的是,只修改主工程并不会对pod库生效,所以还需要对pod库进行处理!

在pod库中一个一个修改太麻烦,并且每次pod install后就会失效,我们需要从podfile文件入手。

post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '10.0'
if config.name == 'Release'
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)', 'NDEBUG=1']
end
end
end
end

判断是否是Release环境,如果是就在build_settings中的GCC_PREPROCESSOR_DEFINITIONS添加$(inherited)NDEBUG=1

$(inherited)是继承Pods工程的设置

添加完后执行pod install,就可以发现每个pod库的Preprocessor Marcos中,在Release下都添加了NDEBUG=1

NSAssert介绍

说完assert,再来说说我们常用的NSAssert

NSAssert细分为

  • NSAssert/NSCAsseet
  • NSParameterAssert / NSCParameterAssert

他们的区别如下:

  1. NSAssert 和 NSParameterAssert 只适用于OC环境中。NSCAsseet 和 NSCParameterAssert 适用于C语言环境中

  2. 当 NSAssert 或 NSParameterAssert 的条件不满足时,断言处理器会调用
    -handleFailureInMethod:object:file:lineNumber:description:方法。
    当 NSCAssert 或 NSCParameterAssert 的条件不满足时,断言处理器会调用 -handleFailureInFunction:file:lineNumber:description: 方法。

NSAssert和NSAssert都有一些变体,例如NSAssert1NSAssert2NSCAssert1NSCAssert2等等。他们的区别是会输出不同的参数。
具体信息详见官方文档:https://developer.apple.com/documentation/foundation/nsassert1

从Xcode4.2以后,会自动在release环境下忽略NSAssert等断言函数。它是通过定义 NS_BLOCK_ASSERTIONS 宏实现的,确保不会对release环境有影响。

我们可以在很多第三方库中看到他们定义的assert,关闭条件也是基于NS_BLOCK_ASSERTIONS

#ifndef _GTMDevAssert
// we directly invoke the NSAssert handler so we can pass on the varargs
// (NSAssert doesn't have a macro we can use that takes varargs)
#if !defined(NS_BLOCK_ASSERTIONS)
#define _GTMDevAssert(condition, ...) \
do { \
if (!(condition)) { \
[[NSAssertionHandler currentHandler] \
handleFailureInFunction:(NSString *) \
[NSString stringWithUTF8String:__PRETTY_FUNCTION__] \
file:(NSString *)[NSString stringWithUTF8String:__FILE__] \
lineNumber:__LINE__ \
description:__VA_ARGS__]; \
} \
} while(0)
#else // !defined(NS_BLOCK_ASSERTIONS)
#define _GTMDevAssert(condition, ...) do { } while (0)
#endif // !defined(NS_BLOCK_ASSERTIONS)

NSAssertNSCAssert底层都是通过NSAssertionHandler来实现的,只有2个实现方法

  • -handleFailureInMethod:object:file:lineNumber:description: NSAssert/NAParamterAssert调用
  • -handleFailureInFunction:file:lineNumber:description: NSCAssert/NACParamterAssert调用

Xcode的开关在

我们也可以自定义NSAssert

继承NSAssertionHandler,重写handleFailureInFunction:file:lineNumber:descriptionhandleFailureInMethod:object:file:lineNumber:description:

在AppDelegate中注册

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
NSAssertionHandler *handler = [[UDTestAssertHandler alloc] init];
[[[NSThread currentThread] threadDictionary] setValue:handler
forKey:NSAssertionHandlerKey];
return YES;
}

NSAssert的坑

NSAssert在OC中,会存在循环引用问题

- (IBAction)buttonAction:(UIButton *)sender {
TestMode *mode = [TestMode new];
self.mode = mode;
// @weakify(self);
mode.textBlock = ^{
// @strongify(self);
int k = 0;
NSParameterAssert(k == 0);
NSLog(@"123");

};
mode.textBlock();
}

循环引用的根源在于NSAssert定义中使用了self

#if !defined(_NSAssertBody)
#define NSAssert(condition, desc, ...) \
do { \
__PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
if (__builtin_expect(!(condition), 0)) { \
[[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd \
object:self file:@(__FILE_NAME__) \
lineNumber:__LINE__ description:(desc), ##__VA_ARGS__]; \
} \
__PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \
} while(0)
#endif

我们可以试用@weakself 和 @strongself来解决。不过在release环境下,assert并不会调用。所以推荐试用NSCAssertNSParameterAssert

最近在做针对国外用户的App,考虑到国外都是Facebook和Twitter等社交平台,所以考虑使用iOS系统中的原生分享。但是在iOS11上面遇到一些坑,遂记录一下。

在iOS11以下的版本中,系统默认集成了社交账号的功能。但是在iOS11中内置的社交账号被移除了。并且在iOS11中废弃了一些方法。

SOCIAL_EXTERN NSString *const SLServiceTypeTwitter NS_DEPRECATED(10_8, 10_13, 6_0, 11_0);
SOCIAL_EXTERN NSString *const SLServiceTypeFacebook NS_DEPRECATED(10_8, 10_13, 6_0, 11_0);
SOCIAL_EXTERN NSString *const SLServiceTypeSinaWeibo NS_DEPRECATED(10_8, 10_13, 6_0, 11_0);
SOCIAL_EXTERN NSString *const SLServiceTypeTencentWeibo NS_DEPRECATED(10_8, 10_13, 6_0, 11_0);
SOCIAL_EXTERN NSString *const SLServiceTypeLinkedIn NS_DEPRECATED(10_8, 10_13, 6_0, 11_0);
Read more »

代码块是Xcode中很便利的一个功能,你可以自定义不同的代码块,特别是对于一些需要重复写的代码,使用代码块可以大大的提高效率。

代码块介绍

代码块这个功能大家其实都用过,当你在写一些代码时候,代码提示功能就会显示出一些系统定义好的代码块。如图就是系统定义好的if else的代码块。

代码块在右侧边栏的下方,在这里你可以看到系统定义的代码块也可以自定义你自己的代码块。

下图就是系统定义好的dispathc_once_t的代码块。

定义自己的代码块

定义自己的代码块有很多好处,你可以随心所欲的根据自己的项目和喜好定义自己的代码块。
比如一些@property属性,一些三方的统计功能等等。下面我们来定义一个代码块,来快速定 义@property 修饰符为nonatomic和strong

首先在任意一个类下面声明如下的属性 type 和 name 为提示语

@property (nonatomic,strong) type name;

然后把type 和 name 上分别改为<#type#> 和 <#name#>

你会发现 type 和name 都变成了高亮状态,你可以按Tab键来切换。


是不是发现变成和系统的一样了?你可以很方便的在高亮地方写入你需要的代码,然后按Tab键来回切换。

然后全选你刚刚写的那行代码,拖拽到代码块界面所在的区域。

然后会弹出一个界面,在这里填写你的代码块的信息。
Title就是在代码块管理区域显示的名称。重要的下面的Completion Shortcut,这就是你的代码块的快捷键,

然后点击Done。你的代码块就会出现在列表中了。去代码区试一下
输入@ps 就会出现你的代码块提示,按回车后刚刚定义的代码就出现了~

代码块的好处是不言而喻的,绝对是提高效率的利器。比如我们之前的项目要接入好多第三方的统计功能,然而每个界面挨个复制挨个改很浪费时间(当然也可以使用runtime直接在Viewcontroller 的load方法写,但是就不能自定义一些东西了)。使用代码块后就会很方便的完成这些工作啦。

再举个栗子。Xcode8以后禁用了第三方插件,之前使用的多行注释插件(就是喵神写的那个)也被废弃了。虽然Xcode8提供了系统的,但是不太好用,你也可以使用代码块来自定义多行注释。
(快捷键定义为 “///“ 并不会按回车就会出来,而是需要按esc才能提示,所以我用了“ccc”)

代码块的存放路径为 ~/Library/Developer/Xcode/UserData/CodeSnippets

大家可以从网上下载一些大神写好的,也可以自己写完以后备份,防止丢失。
上个地址吧,我自己定义的一些,没怎么管理,有点乱

https://github.com/liweican1992/XcodeCodeSnippets.git

PS:关于统计埋点,我自己根据Runtime写了一些统计功能,回头补上。

随着Apple审核时间的大大加快,确实造福了我们这些苦逼的开发者。今天主要聊一些审核被拒以后如果中间存在误会什么的,如何向苹果申诉。
关于申诉,我之前的博客就提及过,详见: 关于审核被拒申诉那点事
但是考虑到时间这么久了,有一些规则可能发生变化,就再重新开一篇。

苹果的审核总是充满了随机性,之前用的好好的一个功能就会忽然告诉你不符合他们的标准了。这次提交审核总共被拒绝了两次,第一次是因为版本号,第二次因为服务器机房挂了。

Read more »

简书旧文

推送服务在日常运营中至关重要,就算是大厂也经常发生推送事故。相信很多人都收到过名为“test”的推送信息。本篇将介绍如何在推送事故发生时“撤回”已发送的推送通知。

细心的用户会发现当微信消息被撤回或者视频请求被取消时,相应的推送信息也会消失不见。哪怕是微信没有在后台运行。

本人很是好奇,网上又没有查到有用的资料,今天花了点时间实现了类似的功能。

Read more »

iOS10已经发了,最近发现App在iOS10上有一些问题,主要是iOS10的字体变了,一些Label要做适配。还有一些相机权限之类的。更加详细的可以参考这篇文章
https://github.com/ChenYilong/iOS10AdaptationTips

之前一直纠结要不要升级Xcode8,毕竟好多插件都不能用了,还有用Xcode7打包的App在iOS10上运行能不能收到推送,经过一天的测试。结果如下:
以下结果是我用极光推送测试的结果(JPush)

Xcode7打包的App能不能再iOS10上收到推送?

答案:可以的,能够收到推送。但是点击推送或者从推送启动App之前的方法不再响应。

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
//iOS7以后收到推送 推送结果会在这里响应
//iOS10 以后不再响应这里
}

也就是只能收到,但是点击推送以后只会打开App而不会走didReceiveRemoteNotification,所以页面不跳转。
(我是用AD_Hoc证书发了一个测试版。用iOS10的机子装上以后使用推送生产环境推送测试的。AppStore版本没测试)

Xcode8升级以后怎么做iOS10适配

我是根据最新的Jpush官方文档做的适配
https://docs.jiguang.cn/jpush/client/iOS/ios_guide_new/

需要注意的几点:

1.Xcode8的疯狂输出模式

刚用Xcoed8时候吓着了,疯狂输出一大堆log信息。建议关闭掉。
关闭办法


进入地方在你选择模拟器的左边有一个你的App图标和名字,点击一下选择Edit Scheme。 然后选择Run->Environment Variables->+号

OS_ACTIVITY_MODE =disable

然后打钩,完事

但是Xcode8的NSLog不打印了 = =!我这边是这个情况

2.Push Notification开关

刚在Xcode8上运行时候肯定会报错。需要在Tragets->Capabilites打开Push Notification开关。


点击修复后会自动生成一个xxx.entitlements的文件(xxx是你的工程名字)


你会发现里面会有APS Environment 并且为development
那你需要上线时候改成发布吗?
并不用可以忽略它,它对你发布并无影响。只要你发布时候选择的是发布证书依旧是发布环境。

但是,如果你用Xcode8生成以后再用Xcode7打开,如果不删除这个文件,打包时候就会报错,所以如果你想用Xcode8做完适配以后还想用Xcode7发包的话请删除掉这个文件。(然后删除掉会报错)把下图选中哪行后面的路径删掉就好了

更新:昨天试了下,删除这个文件再用Xcode7打包以后安装到iOS10可以收到推送但是还是不响应推送处理的方法。
暂时解决办法是使用Xcode7开发最后发版使用Xcode8
Xcode7打包报错应该有解决办法,暂时留个坑

代码适配
因为用的第三方,所以按着文档来就好了,但是也有一些小坑

#ifdef NSFoundationVersionNumber_iOS_9_x_Max
//在这里写针对iOS10的代码或者引用新的API
#import <UserNotifications/UserNotifications.h>

#endif
#ifdef NSFoundationVersionNumber_iOS_9_x_Max
- (void)jpushNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(NSInteger))completionHandler {
    // Required
    NSDictionary * userInfo = notification.request.content.userInfo;
    if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
        [JPUSHService handleRemoteNotification:userInfo];
        [JPUSHService resetBadge];
        ///在前台时候收到推送 iOS10App运行在前台推送来了也能显示哦
    }
    completionHandler(UNNotificationPresentationOptionAlert); // 需要执行这个方法,选择是否提醒用户,有Badge、Sound、Alert三种类型可以选择设置
}

// iOS 10 Support
- (void)jpushNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler {
    // Required
    NSDictionary * userInfo = response.notification.request.content.userInfo;
    if([response.notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
        [JPUSHService resetBadge];
///这个是我自己处理推送的方法 忽略掉
        [self didRegisterFormApnsWithInfo:userInfo];
  [JPUSHService handleRemoteNotification:userInfo];
    }
    completionHandler();  // 系统要求执行这个方法
}
#endif

有趣的是iOS10当App正在运行时候也可以在上方弹出推送框

此文为简书旧文

最近公司开发者账号马上到期了,需要进行续费工作。现在记录一下流程和遇到的一些坑。
首先登陆开发者中心https://developer.apple.com/
选择右上方的Account,然后输入账号和密码。进入后如果过期时间小于30天,在首页会给你一个小弹窗告诉你要续费。

update
补充下图片,当还有一个月过期时候,登录iTunes Connect 或者开发者中心都会有提示信息

Read more »

做iOS开发已经有几年了,中间也积累下了不少工具
做一个总结吧

一、Mac 菜单栏工具

  • Bartender

Barteder是一款优秀的菜单栏管理工具,可以把一些不常用的隐藏起来,将大量的菜单有选择折进 Bartender 的二级菜单,不仅会看起来更整洁,使用起来也不会造成太大的麻烦。配合下面的 iStar Menus 食用更加
官网地址:https://www.macbartender.com/

  • iStar Menus


这个工具可以监测Mac的各种性能,并且可以就改样式颜色等。装逼利器之一~


官网地址:https://bjango.com/mac/istatmenus/

Read more »