做过后端(J2EE),11年转 iOS ,12年中~14年中创过业(跨界开火锅店..),对代码有洁癖,致力于写出优雅的代码,在多个公司负责过 iOS 架构,熟悉设计模式,热衷产品设计与交互,业余时间担任 CSDN iOS 版块版主
Houston 的背景 Houston 在 GitHub 上的地址:https://github.com/nomad/Houston,作者又是Mattt Thompson,简直是惨无人道啊,又高产,又有质量 Houston 能让我们在本地、甚至终端很方便的调试远程通知。 安装 首先在终端安装: $ gem install houston 我第一次安装的时候报了错,但是第二次就安装成功了: ... Installing ri documentation for houston-2.2.3 1 gem installed 安装成功后执行gem list命令可以看到 houston 和它的版本号: 然后也能执行apn命令了: $ apn error: undefined method `size' for nil:NilClass. Use --trace to view backtrace 准备证书 在正式的使用之前需要先准备好pem证书文件: 在Keychain Access里选择你的Push Services证书和通过它左侧的小箭头展开的私钥文件 选择这两个文件后,右键选择Export 2 items... 选择p12格式并将导出的文件命名为cert.p12 最后将这个p12文件转成pem文件 $ openssl pkcs12 -in cert.p12 -out apple_push_notification.pem -nodes -clcerts 如果你的 Keychain Access 里没有 Push Services 证书的话,你可能需要下载或创建一个: 1. 登录到 iPhone Developer Connection Portal(http://developer.apple.com/iphone/manage/overview/index.action )并点击 App IDs 2. 创建一个不使用通配符的 App ID ,这是因为通配符 ID 不能用于推送通知服务 3. 点击 App ID 旁的“Configure”,然后按下按钮生产 推送通知许可证。根据“向导” 的步骤生成一个签名并上传,最后下载生成的许可证 4. 通过双击 .cer 文件将你的 aps_developer_identity.cer 添加到 Keychain Access中 5. 添加后Push Services证书后就开始pem文件,步骤同上 然后就能在终端测试远程通知了。 发送远程通知 需要在apn命令里指定DeviceToken和pem证书路径。 普通文本 比如测试一下普通的文本通知: $ apn push "<d10c67b0 079cfdc0 27b5dd81 2fd537ce 16831f40 ca55fa34 8b14ffde 626435f6>" -c ~/Desktop/apple_push_notification.pem -m "Hello" 成功后的终端提示: 1 push notification sent successfully 自定义参数 带参数的远程通知: $ apn push "<d10c67b0 079cfdc0 27b5dd81 2fd537ce 16831f40 ca55fa34 8b14ffde 626435f6>" -c ~/Desktop/apple_push_notification.pem -m "Hello" -d content-id=42 自定义多个参数 多个参数之间用逗号分隔: $ apn push "<d10c67b0 079cfdc0 27b5dd81 2fd537ce 16831f40 ca55fa34 8b14ffde 626435f6>" -c ~/Desktop/apple_push_notification.pem -m "Hello" -d content-id=42,icon=image.png 静默通知 $ apn push "<d10c67b0 079cfdc0 27b5dd81 2fd537ce 16831f40 ca55fa34 8b14ffde 626435f6>" -c ~/Desktop/apple_push_notification.pem -s "" -n 静默通知要把 sound 设为空字符串,并且要启用content_available。 终端可用选项 ‘-m’, ‘–alert ALERT’, ‘Body of the alert to send in the push notification’ ‘-b’, ‘–badge NUMBER’, ‘Badge number to set with the push notification’ ‘-s’, ‘–sound SOUND’, ‘Sound to play with the notification’ ‘-y’, ‘–category CATEGORY’, ‘Category of notification’ ‘-n’, ‘–[no]-newsstand’, ‘Indicates content available for Newsstand’ ‘-d’, ‘–data KEY=VALUE’, Array, ‘Passes custom data to payload (as comma-delimited “key=value” declarations)’ ‘-P’, ‘–payload PAYLOAD’, ‘JSON payload for notifications’ ‘-e’, ‘–environment ENV’, [:production, :development], ‘Environment to send push notification (production or development (default))’ ‘-c’, ‘–certificate CERTIFICATE’, ‘Path to certificate (.pem) file’ ‘-p’, ‘–[no]-passphrase’, ‘Prompt for a certificate passphrase’
此前我对创新的定义是:做出别人完全没想到的东西,要牛逼,要与众不同,借鉴过的东西都不能称之为创新。这个定义真的太过狭隘,狭隘到让创新那么遥不可及,当我看过这本书后,对创新的产生过程又有了新的理解,其实产生创新时的最初想法就是那么幼稚、不科学,说出去都怕别人笑话,但是如果你自己相信它可以做到,并为之实践,或许它就真的可以做到。而且创新其实是可以被借鉴的,甚至创新就是从模仿开始的,只要能改善现有的成功模式,只要能在某个点胜过别人就是创新。 创意不是来自于经验 我曾经认为一个好的产品经理应该是对产品定位很明确、对面向的用户群体很明确,对行业、市场不说十分明确,至少也要有自己的独到见解,此外对前端app来说,各平台的差异性、甚至APIs也要有所了解,为什么要了解APIs?因为这基本决定了一个产品能做成什么样。不过现在我想我可能要把平台差异和APIs这两个加成项去掉了:符合平台用户操作习惯没问题,但是这不是必要条件。好比iOS4连导航栏都不能自定义,不照样出了像Path这样的app。 创意可能会被经验束缚 工作中我一直认为自己是有产品观念的人,能提出建议并致力于改善体验,也希望团队的创新氛围能更加活跃,但是这本书让我遗憾的发现我在工作中实际上有很多扼杀创新的行为。。典型的就是自以为是,容易相信自己的经验。拿最近的一件事来说,曾经一起共事的一位CEO现在在做金融的一级市场,他想在客户的某个聊天群里插入一个机器人,专门抓取群里的消息,以便分析出这些客户对哪些东西感兴趣,我当时想了想后就说除非这群是自家开的,便否定了这种做法,那到底能不能实现呢?其实我内心觉得也是有可能的,当时想到了两种方案,不过因为市场上没有类似的做法,还有被屏蔽的可能,我就认为这是一个不合理的、脱离实际的、完全外行的想法,我以为我是拒绝了不合理的需求,其实我是否定了别人对于创新的尝试,却没有意识到自己的思维已经被经验所限制住了。 酒香也怕巷子深 除了思维这个创意的起源外,技术是成功创意的保障,把自己培养成既有创意又有技术的人很难,不过很有必要,爱迪生发明电灯之前、张衡发明地动仪之前都是经过了孤身一人的长久研究,在没有人相信自己创意的时候,只好自己来证明。 这本书虽说不能让你看完就有了创新的保障,但是会让你离创新的路途更进一步,目的地明确了,就不要吝啬自己的投入:思维就像水龙头,技术就像水桶,如果没有水,水桶没什么意义;如果你思维敏捷,创意无限,桶太小,流出去的水也不是你的。 序言中那些励志的原则: 1. 科学工作者申明某件事情可行的时候,基本上他不会错,但当他说不可能的时候,他很可能错了。 2. 发现极限的唯一方法就是超越极限,尝试向稍微超越这个极限的领域迈进、冒险。 3. 无论是哪种技术,只要它是非常先进的,那看起来都跟魔术没什么区别。
用iTunes Connect提供的TestFlight功能可以确保我们在设备上测试的版本和App Store上将要发行的版本是同一个。 TestFlight仅支持iOS 8及后续版本,并且需要从App Store里安装TestFlight app。 分为内部测试和外部测试两种: 内部测试 每个应用最多25位测试者,需要把测试者的Apple ID添加到开发者账号里,苹果为会测试者创建一个iTunes Connect账号并与主账号关联。 只有Admin、Legal 、Technical角色才能测试,内部测试者在接收到邮件后,必须用iOS设备打开该邮件才能测试,不用等Apple审核。 每个账号能测试 10 个设备,但是每条邀请只能使用一次,我是通过反复邀请来添加每个账号的测试设备: Devices (2) iPhone 6 Plus (iOS 8.3) iPhone 5 (iOS 8.3) 外部测试 最多能邀请1000个测试者,预发布版本的第一个Build必须被Apple完全审核通过后才能测试,之后的同一个版本的其他Build不需要完全审核。 注意: 一个Apple ID,不能同时是内部、外部测试者。 安装的测试App名称旁有个橙色小点。 官方参考资料,有详细步骤:BetaTestingTheApp
实体的模型定义: 实体的class定义: @objc(ImageEntity) class ImageEntity: NSManagedObject { @NSManaged var imageData: NSData } 存储: @IBAction func saveImageToCoreData() { let delegate = UIApplication.sharedApplication().delegate as AppDelegate let context = delegate.managedObjectContext let imageData = UIImagePNGRepresentation(UIImage(named: "image")) let imageEntity = NSEntityDescription.entityForName("ImageEntity", inManagedObjectContext: context!) let image = ImageEntity(entity: imageEntity!, insertIntoManagedObjectContext: context!) image.imageData = imageData var error: NSError? if context!.save(&error) == false { println("failed: \(error!.localizedDescription)") } } 读取: @IBAction func loadImageFromCoreData() { let delegate = UIApplication.sharedApplication().delegate as AppDelegate let context = delegate.managedObjectContext let request = NSFetchRequest(entityName: "ImageEntity") var error: NSError? let imageEntities = context?.executeFetchRequest(request, error: &error) let imageEntity = imageEntities?.first! as ImageEntity self.imageView.image = UIImage(data: imageEntity.imageData) } Demo地址
通过Bridging-Header文件,Swift可以与Objective-C无缝调用,但是Swift与Objective-C有一个很大的不同点:Swift支持Optional类型。比如NSView和NSView?,在Objective-C里对此只有一种表示,即NSView *,既可以用来表示该View为nil、也能表示为非nil,此时Swift编译器是无法确定这个NSView是不是Optional类型的,这种情况下Swift编译器会把它当作NSView!处理,隐式拆包。 在早期发布的Xcode版本中,苹果的一些框架针对Swift的Optional类型进行了一些专门审查,使他们的API能够适配Optional,而Xcode 6.3的发布,给我们带来了Objetive-C的一个新特性:nullability注解,利用该特性我们也能对自己的代码进行类似的处理。 核心:__nullable 和 __nonnull 这个功能给我们带来了两个新的类型注解:__nullable和__nonnull,就像你看到的,__nullable可以表示一个NULL或者nil值,而__nonnull则刚好相反。如果你违反了这个规则,你将会收到编译器的警告: @interface AAPLList : NSObject <NSCoding, NSCopying> //--- - (AAPLListItem * __nullable)itemWithName:(NSString * __nonnull)name; @property (copy, readonly) NSArray * __nonnull allItems; //--- @end //-------------- [self.list itemWithName:nil]; // warning! 你能在任何地方使用__nullable和__nonnull关键字,比如和标准C的const一起使用,也能直接应用到指针上。但是在大多数情况下,你会以优雅的方式写下这些注解:在方法定义或声明里,只要类型是一个简单的对象或者Block指针,你就能以不带下划线的方式(nullable或nonnull)直接写在左括号后面: - (nullable AAPLListItem *)itemWithName:(nonnull NSString *)name; - (NSInteger)indexOfItem:(nonnull AAPLListItem *)item; 对于@property,你也能以同样的方式写在它的属性列表里: @property (copy, nullable) NSString *name; @property (copy, readonly, nonnull) NSArray *allItems; 不带下划线的形式比带下划线的形式看起来更简洁,但你仍然需要将它们应用到头文件的每一个类型里。如果你觉得麻烦同时想让头文件变得更加简洁,你就会使用到审查区域。 审查区域(Audited Regions) 如果想更加轻松的添加这些注解,那么你可以把Objective-C头文件的某个区域标记为需要审查(for nullability),在这个区域内,所有简单的指针类型都会被当作nonnull,我们之前的例子会变成这样: NS_ASSUME_NONNULL_BEGIN @interface AAPLList : NSObject <NSCoding, NSCopying> //--- - (nullable AAPLListItem *)itemWithName:(NSString *)name; - (NSInteger)indexOfItem:(AAPLListItem *)item; @property (copy, nullable) NSString *name; @property (copy, readonly) NSArray *allItems; //--- @end NS_ASSUME_NONNULL_END // -------------- self.list.name = nil; // okay AAPLListItem *matchingItem = [self.list itemWithName:nil]; // warning! Xcode 6.3(iOS 8.3 SDK)引入了NS_ASSUME_NONNULL_BEGIN / END宏 其中itemWithName方法的name参数没有使用Nullability特征,但是会被当作nonnull处理 为了安全起见,这个规则也有一些例外情况: typedef定义的类型不会继承nullability特性—它们会轻松地根据上下文选择nullable或non-nullable,所以,就算是在审查区域内,typedef定义的类型也不会被当作nonnull。 像id *这样更复杂的指针类型必须被显式地注解,比如,你要指定一个nonnull的指针为一个nullable的对象引用,那么需要使用__nullable id * __nonnull。 像NSError **这些特殊的、通过方法参数返回错误对象的类型,将总是被当作是一个nullable的指针指向一个nullable的指针:__nullable NSError ** __nullable。 你可以通过Error Handling Programming Guide了解更多详细内容。 兼容性 你的Objective-C框架现有的代码写对了吗?是否能安全的改变它们的类型? Yes, it is. 现有的、被编译过的代码还能继续使用你的框架,也就是说ABI没有变化(编译器不会报错),这也意味着现有的代码不会在运行时捕获到nil的不正确传值。 用新的Swift编译器编译现有的源码,并在使用你的框架的时候,可能会因为一些不安全的行为在编译时得到额外的警告。 nonnull不影响优化,尤其是你还可以在运行时检查标记为nonnull的参数是否为nil,这可能需要必要的向后兼容。 大多数情况下,应该接受nullable和nonnull,你当前所使用的断言或者异常太粗暴了:违反约定是程序员经常犯的错误(而nullable和nonnull能在编译时就解决问题)。特别的,返回值是你能控制的东西,永远不应该对一个non-nullable的返回类型返回一个nil,除非这是为了向后兼容。 回到Swift 现在我们给我们的Objective-C头文件添加了nullability注解,我们在Swift中使用它: 在Objective-C中添加注解之前: class AAPLList : NSObject, NSCoding, NSCopying { //--- func itemWithName(name: String!) -> AAPLListItem! func indexOfItem(item: AAPLListItem!) -> Int @NSCopying var name: String! { get set } @NSCopying var allItems: [AnyObject]! { get } //--- } 添加注解之后: class AAPLList : NSObject, NSCoding, NSCopying { //--- func itemWithName(name: String) -> AAPLListItem? func indexOfItem(item: AAPLListItem) -> Int @NSCopying var name: String? { get set } @NSCopying var allItems: [AnyObject] { get } //--- } 这些Swift代码非常清晰。只有一些细节的变化,但是它让你的框架使用起来更爽。