ACChe

ACChe 查看完整档案

广州编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

举头思算法,低头写代码...

个人动态

ACChe 赞了回答 · 2016-02-19

解决beego的orm每update,delete,read的时候都要给主键值????

beego的orm默认应该把id int 作为主键

read, update, delete的时候必须有主键,应该只有create的时候不需要,因为主键是auto的。你主键设置是正确的

Uid        int `orm:"pk"`

其实建议主键是这样的

Id int

然后这段代码,比如read,beego orm 执行的时候,会找到主键,获取主键的值value,然后执行
select fields from user where uid = value ,你的主键的值不存在(0),orm就没法执行select, 当然会报错

    u := Userinfo{Username: "hello"}
    err = o.Read(&u)
    fmt.Println(u)
    fmt.Printf("ERR: %v\n", err)

    num, err = o.Delete(&u)
    fmt.Printf("NUM: %d,  Err: %v\n", num, err)

关注 3 回答 3

ACChe 赞了问题 · 2016-02-19

解决beego的orm每update,delete,read的时候都要给主键值????

```go
package main

import (
    "fmt"
    "github.com/astaxie/beego/orm"
    _ "github.com/go-sql-driver/mysql"
    "time"
)

type Userinfo struct {
    Uid        int `orm:"pk"`
    Username   string
    Departname string
    Created    time.Time
}

func init() {
    orm.RegisterModel(new(Userinfo))
    orm.RegisterDataBase("default", "mysql", "momaek:123456@/test?charset=utf8")

}

func main() {
    o := orm.NewOrm()
    user := Userinfo{Username: "hello", Departname: "wd"}
    id, err := o.Insert(&user)
    fmt.Printf("ID:%d, Err: %v\n ", id, err)

    user.Uid = 3
    user.Created = time.Now()
    user.Departname = "wakkkk"
    num, err := o.Update(&user)
    fmt.Printf("Num:%d ,ERR: %v\n", num, err)

    u := Userinfo{Username: "hello"}
    err = o.Read(&u)
    fmt.Println(u)
    fmt.Printf("ERR: %v\n", err)

    num, err = o.Delete(&u)
    fmt.Printf("NUM: %d,  Err: %v\n", num, err)

}

```

代码是这样的。然后运行报错:

ID:47, Err: <nil>
Num:0 ,ERR: <nil>
{0 hello  0001-01-01 00:00:00 +0000 UTC}
ERR: missed pk value
NUM: 0,  Err: missed pk value

关注 3 回答 3

ACChe 关注了问题 · 2016-02-19

解决beego的orm每update,delete,read的时候都要给主键值????

```go
package main

import (
    "fmt"
    "github.com/astaxie/beego/orm"
    _ "github.com/go-sql-driver/mysql"
    "time"
)

type Userinfo struct {
    Uid        int `orm:"pk"`
    Username   string
    Departname string
    Created    time.Time
}

func init() {
    orm.RegisterModel(new(Userinfo))
    orm.RegisterDataBase("default", "mysql", "momaek:123456@/test?charset=utf8")

}

func main() {
    o := orm.NewOrm()
    user := Userinfo{Username: "hello", Departname: "wd"}
    id, err := o.Insert(&user)
    fmt.Printf("ID:%d, Err: %v\n ", id, err)

    user.Uid = 3
    user.Created = time.Now()
    user.Departname = "wakkkk"
    num, err := o.Update(&user)
    fmt.Printf("Num:%d ,ERR: %v\n", num, err)

    u := Userinfo{Username: "hello"}
    err = o.Read(&u)
    fmt.Println(u)
    fmt.Printf("ERR: %v\n", err)

    num, err = o.Delete(&u)
    fmt.Printf("NUM: %d,  Err: %v\n", num, err)

}

```

代码是这样的。然后运行报错:

ID:47, Err: <nil>
Num:0 ,ERR: <nil>
{0 hello  0001-01-01 00:00:00 +0000 UTC}
ERR: missed pk value
NUM: 0,  Err: missed pk value

关注 3 回答 3

ACChe 关注了问题 · 2016-01-19

解决npm update 失败,求解决方法

执行命令

npm -g update

提示

npm ERR! Object.keys called on non-object
npm ERR! 
npm ERR! If you need help, you may report this error at:
npm ERR!     <http://github.com/npm/npm/issues>
npm ERR! Darwin 14.0.0
npm ERR! argv "node" "/usr/local/bin/npm" "-g" "update"
npm ERR! node v0.10.34
npm ERR! npm  v2.1.14

npm ERR! Object.keys called on non-object
npm ERR! 
npm ERR! If you need help, you may report this error at:
npm ERR!     <http://github.com/npm/npm/issues>
npm ERR! Darwin 14.0.0
npm ERR! argv "node" "/usr/local/bin/npm" "-g" "update"
npm ERR! node v0.10.34
npm ERR! npm  v2.1.14

npm ERR! Object.keys called on non-object
npm ERR! 
npm ERR! If you need help, you may report this error at:
npm ERR!     <http://github.com/npm/npm/issues>

npm ERR! Please include the following file with any support request:
npm ERR!     /Library/WebServer/Documents/wordpress/npm-debug.log

好像不是网络问题,VPN也不好使,请问怎么解决?

关注 7 回答 5

ACChe 回答了问题 · 2015-12-08

iOS app上传后ITC.apps.preReleaseBuild.errors.invalidBinary

1,你上传的时候网络不稳定,可以换个上网环境或者mac来上传。
2,用application loader来上传
3,可以尝试用shenzhen这个来上传

一般你看到icon正常显示的话,就是成功上传了。

关注 4 回答 3

ACChe 关注了问题 · 2015-12-08

golang写分布式爬虫

自己写写练手,用golang写的,已经本机实现了单线程和多线程爬虫,当然,写爬虫最终肯定要写分布式才可以。可是我对分布式概念很模糊,不知道如何下手才好,不想找框架来写,只是想大体跟思路走就可以。

希望求得一个好的解决方案。

现在的有个想法是我可以设置一个master和多个slave。master只负责分配任务,slave负责爬虫,slave将master分配过来的任务爬一次,然后将获得的url再返回给master。这样就需要master起一个server。slave如果没有任务就需要不停的向master请求任务。url去重等任务都在master处理。

只是自己YY,希望推荐一个好的解决思路。

关注 6 回答 1

ACChe 回答了问题 · 2015-11-27

解决提交 App Store 遇到 ITMS-90096 问题?

按照错误提示里的阐述,有以下步骤:
1,图片描述

图片描述

2,将启动图(.png格式)放到项目顶层文件夹内,而不是在Images.xcassets
3,在info.plist里添加设置:
图片描述

关注 4 回答 2

ACChe 关注了问题 · 2015-11-26

不使用依赖注入到处new的优缺点?

看到新公司里的代码都是new xxService, 而不直接用依赖注入,这是为什么?

关注 9 回答 6

ACChe 关注了问题 · 2015-11-26

解决提交 App Store 遇到 ITMS-90096 问题?

图片描述

提交的时候出现这个错误,网上搜了好多也没解决。
我用的是LaunchSreen.xib,里面放了张启动图片,图片的大小用约束设置的,工程里没有建 LaunchImage

关注 4 回答 2

ACChe 赞了问题 · 2015-11-26

解决提交 App Store 遇到 ITMS-90096 问题?

图片描述

提交的时候出现这个错误,网上搜了好多也没解决。
我用的是LaunchSreen.xib,里面放了张启动图片,图片的大小用约束设置的,工程里没有建 LaunchImage

关注 4 回答 2

ACChe 关注了问题 · 2015-11-10

解决OC调用Swift问题

配置如下:
clipboard.png
项目-swift.h文件内容(貌似为空):
clipboard.png
Swift文件(外部添加的)

clipboard.png

就是这样,看这教程里生成的项目-swift.h文件里有那些类,我这里却没有,请大家帮忙分析一下原因。
项目完全是Objective-C,后期添加了Swift文件。

关注 5 回答 4

ACChe 赞了文章 · 2015-11-09

iOS:ComponentKit 使用总结

前言的前言

好。。开始做下ComponentKit使用总结。。源码没有看,只看了一些概念以及API。本篇文章主要总结下使用心得以及ComponentKit的理念。一切的分析都基于使用层面上的。。大神请打脸或略过~

本文面向有一定开发经验的iOSer,以及喜欢折腾的iOSer...

前言

传统MVC模式,数据(s)-控制器(s)-视图(s)之间的双向流所产生的大量状态将导致:
1)代码激增
2)BUG出现得概率增大
3)视图渲染效率低
4)APP流畅度不高(特指ScrollView不能达到60FPS)
所以我们需要一个更为简单粗暴的框架来写我们的APP。。(真正的原因,绝不会告诉你其实只是被要求...)

理念

既然名字叫做ComponentKit,自然先说说Component(元素)。对于开发者来说,所有的图层(可见/不可见)其实都是由一个元素排版而来。不同的元素根据不同的排版展示出不同的视图。我做个类比:正如中国四大发明之一-活字印刷一样通过改变排版可以展示不同的文字内容。
这里引用文档的一句话:

A simple analogy is to think of a component as a stencil: a fixed description that can be used to paint a view but that is not a view itself.

意思也大概如此Component不并不直接当做视图(印刷出来的东西)展示,而是告诉你视图长什么样(印刷模具的存在)~

特性

三大特性:(不明觉厉的地方)

  • 描述性:通过stack为主(这里我翻译成“(纵向或者横向)堆砌”)排版模具来告诉我们某一个元素A的子元素在A中如何排列。
  • 函数式:保证数据流是单向的,也就是数据决定Component。比如方程“1 + X”,如果X=2或者X=3相对应结果“1 + 2”与“1 + 3”是固定的一样。数据如果确定了,那么结果就是不变的。当数据发生改变的时候,对应的component会进行重新渲染。(这里FB宣称该框架会尽量少的重新渲染,没有读过代码,没有发言权)
  • 可组合:这里可以想下积木,有些部分写成component,其他地方可以重用。

个人使用的心得?:数据单向流,好处无非在于什么样的数据决定什么样的视图,我们可以无视很多各种交互产生的状态,而仅仅只需要把精力放在数据层上,写好排版方程(functional)似乎好像可以做到一劳永逸。但是正因为如此,ComponentKit在写动画的时候注定较麻烦,因为数据变化是连续的~~也就是model是不断变化的。使用上可以做一些取舍。用ComponentKit的好处就在于写代码可以处于无脑状态,抓着绳子(数据)的一端就好,不容易打死结~

至于动画方面的解释,FB如是说:

Dynamic gesture-driven UIs are currently hard to implement in ComponentKit; consider using AsyncDisplayKit.

API (官方文档内容)

CKComponent

上面说了Component是不可变的,且其可以在任何线程进行创建,避免了出现在主线程的竞争。

这里主要是两个API:

/** Returns a new component. */
+ (instancetype)newWithView:(const CKComponentViewConfiguration &)view
                       size:(const CKComponentSize &)size;

/** Returns a layout for the component and its children. */
- (CKComponentLayout)layoutThatFits:(CKSizeRange)constrainedSize
                         parentSize:(CGSize)parentSize;

一个用来创建Component,一个用来进行排版。

Composite Components

这里只有一句话重点:任何情况自定义component下不要继承CKComponent,而是继承Composite Components
大概原因就是不要污染清纯的父类Component,不要因为一个简单得需求而直接进行继承并重写父类方法(很多弊端,FB blabla),而应该采用修饰的手段来达成(装饰设计模式?)。

这里给出坏代码以及推荐代码示例:

// 不推荐的坏代码:
@implementation HighlightedCardComponent : CardComponent
- (UIColor *)backgroundColor
{
  // This breaks silently if the superclass method is renamed.
  return [UIColor yellowColor];
}
@end
// 推荐代码:
@implementation HighlightedCardComponent : CKCompositeComponent
+ (instancetype)newWithArticle:(CKArticle *)article
{
  return [super newWithComponent:
          [CardComponent
           newWithArticle:article
           backgroundColor:[UIColor yellowColor]]];
}
@end

Views

创建一个元素的类方法

+ (instancetype)newWithView:(const CKComponentViewConfiguration &)view
                       size:(const CKComponentSize &)size;

这里说下第一个参数告诉CK用什么图层类,第二个参数告诉CK如何配置这个图层类。
举个栗子

[CKComponent 
 newWithView:{
   [UIImageView class],
   {
     {@selector(setImage:), image},
     {@selector(setContentMode:), @(UIViewContentModeCenter)} // Wrapping into an NSNumber
   }
 }
 size:{image.size.width, image.size.height}];

同样可以设置空值,举个栗子:

[CKComponent newWithView:{} size:{}]
// 更为直接
[CKComponent new]

Layout && Layout Components

与UIView中的layoutSubViews对应的是CK中的
layoutThatFits:

这里主要介绍几个常用的Layout Components

  • CKStackLayoutComponent 横向或者纵向堆砌子元素
  • CKInsetComponent内陷与大苹果内陷相似
  • CKBackgroundLayoutComponent 扩展底部的元素作为背景
  • CKOverlayLayoutComponent 扩展覆盖层的元素作为遮罩
  • CKCenterLayoutComponent 在空间内居中排列
  • CKRatioLayoutComponent 有比例关系的元素
  • CKStaticLayoutComponent 可指定子元素偏移量

响应者链 && Tap事件 && 手势支持

响应者链

FB中的响应者链与苹果类似,但是两者是分离的。
FB中的链大概长相:
儿子component-> 儿子componentController(如果有) -> 父亲component-> 父亲componentController(如果有) -> (...递归 blabla) -> 【通过CKComponentActionSend桥接】-> (过程:找到被附着的那个View,通过这个View找到最底层的叶子节点ViewA -> (往上遍历ViewA的父亲ViewB -> (...递归 blabla)。

这里一个要点是component不是UIResponder子类,自然无法成为第一响应者~

点击事件

解决发生在UIControl视图上的点击事件很简单,只要将某个SEL绑定到CKComponentActionAttribute即可,在接收外界UIControlEvent时候触发:

@implementation SomeComponent

+ (instancetype)new
{
  return [self newWithView:{
    [UIButton class],
    {CKComponentActionAttribute(@selector(didTapButton))}
  }];
}

- (void)didTapButton
{
  // Aha! The button has been tapped.
}

@end

手势

以上对UIControl适用,一般View则要使用绑定一些更牛逼,更直接的属性,比如tap手势绑定SEL到CKComponentTapGestureAttribute,代码如下:

@implementation SomeComponent

+ (instancetype)new
{
  return [self newWithView:{
    [UIView class],
    {CKComponentTapGestureAttribute(@selector(didTapView))}
  }];
}

- (void)didTapView
{
  // The view has been tapped.
}

@end

Component Actions

一句话?元素Action机制 就是通过无脑绑定SEL,顺着响应链找到可以响应该SEL的元素。

State (TO DO)

对iOS中容器类视图的支持(UITableView, UICollectionView)

概述

FB也很淫荡的做了广告:

ComponentKit really shines when used with a UICollectionView.

吐槽下,之所以特地强调,那是必须啊,任何一款APP都特么离不开UITableView或者UICollectionView。只要会UITableView或者UICollectionView那就具备了独立开发的能力,精通这两者的就算具备了犀利哥的潜质。

FB鼓吹的优点:

  1. 自动重用
  2. 流畅的滑动体验 -> CK自身保证非UI相关的计算全在次线程
  3. 数据源 这个模块由CKComponentDataSource负责。

PS:CKComponentDataSource模块的主要功能:
1)提供输入数据源的操作指令以及数据
2)变化后的数据源布局后台生成
3)提供UITableView或者UICollectionView可用的输出数据源

CKComponentCollectionViewDataSource

CKComponentCollectionViewDataSourceCKComponentDataSource的简单封装。
存在价值:

  1. 负责让UICollectionView适时进行添加/插入/更新“行”,“段”。
  2. 负责提供给UICollectionView“行”和“段“排版信息。
  3. UICollectionView可见行讲同步调用cellForItemAtIndexPath:
  4. 保证返回配置好的cell

这里UICollectionViewCKCollectionViewDataSource数据表现来说仍是单向的。

基础

Component Provider

CKCollectionViewDataSource负责将每一个数据丢给元素(component)进行自我Config。在任何时候需要有某一个元素(component)需要数据进行配置将会把CKCollectionViewDataSource提供的数据源通过CKComponentProvider提供的类方法传入:

 @interface MyController <CKComponentProvider>
    ...
    @end

    @implementation MyController
    ...
    + (CKComponent *)componentForModel:(MyModel*)model context:(MyContext*)context {
        return [MyComponent newWithModel:model context:context];
    }
    ...
  • 用类方法不用block 为了保证数据是不可变的
  • 上下文 这里可以是任意不可以变对象,其被CKCollectionViewDataSource带入。它一般是:1)设备类型 2)外部依赖 比如图片下载器

创建CKCollectionViewDataSource

- (void)viewDidLoad {
    ...
    self.dataSource = _dataSource = [[CKCollectionViewDataSource alloc] initWithCollectionView:self.collectionView supplementaryViewDataSource:nil componentProvider:[self class] context:context cellConfigurationFunction:nil];

添加/修改

需要做的就是将Model与indexPath进行绑定:

- (void)viewDidAppear {
        ...
        CKArrayControllerSections sections;
        CKArrayControllerInputItems items;
        // Don't forget the insertion of section 0
        sections.insert(0);
        items.insert({0,0}, firstModel);
        // You can also use NSIndexPath
        NSIndexPath indexPath = [NSIndexPath indexPathForItem:1 inSection:0];
        items.insert(indexPath, secondModel);
        [self.dataSource enqueueChangeset:{sections, items} constrainedSize:{{0,0}, {50, 50}}];
    }

比如indexPath(0, 0),model是一个字符串“我是0段0行”,告诉CKCollectionViewDataSource将他们绑在一起。

排版

因为无脑,只贴代码:

 - (CGSize)collectionView:(UICollectionView *)collectionView
                 layout:(UICollectionViewLayout *)collectionViewLayout
                 sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
        return [self.dataSource sizeForItemAtIndexPath:indexPath];
    }

事件处理

因为无脑,只贴代码:

 - (void)dataSource:(CKCollectionViewDataSource *)dataSource didSelectItemAtIndexPath:(NSIndexPath *)indexPath
    {
        MyModel *model = (MyModel *)[self.dataSource modelForItemAtIndexPath:indexPath];
        NSURL *navURL = model.url;
        if (navURL) {
            [[UIApplication sharedApplication] openURL:navURL];
        }
    }

(数据源)改变集API

这里主要是指与数据源交互部分的API,主要分为三类:

  1. 动作(针对行的插入/删除/更新,针对段的插入/删除)
  2. 位置指定(行/段位置指定)
  3. 分配数据(丢给Component用的)

贴代码:

CKArrayControllerInputItems items;
// Insert an item at index 0 in section 0 and compute the component for the model @"Hello"
items.insert({0, 0}, @"Hello");
// Update the item at index 1 in section 0 and update it with the component computed for the model @"World"
items.update({0, 1}, @"World");
// Delete the item at index 2 in section 0, no need for a model here :)
Items.delete({0, 2});

Sections sections;
sections.insert(0);
sections.insert(2);
sections.insert(3);

[datasource enqueueChangeset:{sections, items}];

这里需要注意的是:

1)

The order in which commands are added to the changeset doesn't define the order in which those changes will eventually be applied to the UICollectionView (same for UITableViews).

加入changeset的顺序呢并不代表最终UICollectionView最终应用上的改变顺序。

2)
记得初始化的时候执行sections.insert(0);

3)
因为所有的改变集都是异步计算的,所以要小心数据与UI不同步的问题出现
3.1)
始终以datasource为唯一标准,不要试图从曾经的数据源like下例中的_listOfModels获取model:

@implementation MyAwesomeController {
    CKComponentCollectionViewDataSource *_datasource;
    NSMutableArray *_listOfModels;
}

例子中的_datasource才是正房,_listOfModels是小三。
坚持使用

[datasource objectAtindexPath:indexPath];

3.2)
不要执行像:Items.insert({0, _datasource.collectionView numberOfItemsInSection});的语句,因为你所希望插入的位置未必是你想要插入的位置。

种子

Facebook's iOS Infrastructure - @Scale 2014 - Mobile
OJBC.IO ComponentKit介绍
官方文档
Making News Feed nearly 50% faster on iOS
Flexbox排版

查看原文

赞 4 收藏 19 评论 1

ACChe 赞了回答 · 2015-11-09

解决app的电影选座功能是怎么做的呢(iOS版),已经解决!

关注 9 回答 4

ACChe 赞了文章 · 2015-10-23

iOS开发常用高效率宏

前言

github:https://github.com/koknine (终于改成以前的了)

iOS开发当中,有很多用的多但是写起来比较繁琐的代码,比如获取屏幕的宽高,简直纠结的不行,之前在CSDN上面看到很多不错的总结。

iOS开发常用宏这里面就总结的很好,但是为什么字那么小。。。

我在这里重新录入,并添加一些自己写的,后续会继续更新一些好用的宏。

尺寸宏

#define StatusBar_HEIGHT 20

#define NavigationBar_HEIGHT 44

#define NavigationBarIcon 20

#define TabBar_HEIGHT 49

#define TabBarIcon 30

#define SCREEN_WIDTH ([UIScreen mainScreen].bounds.size.width)

#define SCREEN_HEIGHT ([UIScreen mainScreen].bounds.size.height) 

打印宏

//替换NSLog来使用,debug模式下可以打印很多方法名,行信息。
#ifdef DEBUG
#   define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#else  
#   define DLog(...)  
#endif
//直接替换NSLog
#if DEBUG  
#define NSLog(FORMAT, ...) fprintf(stderr,"\nfunction:%s line:%d content:%s\n", __FUNCTION__, __LINE__, [[NSString stringWithFormat:FORMAT, ##__VA_ARGS__] UTF8String]);  
#else  
#define NSLog(FORMAT, ...) nil  
#endif
//DEBUG  模式下打印日志,当前行以及弹出一个警告
#ifdef DEBUG
#   define ULog(fmt, ...)  { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:[NSString stringWithFormat:@"%s\n [Line %d] ", __PRETTY_FUNCTION__, __LINE__] message:[NSString stringWithFormat:fmt, ##__VA_ARGS__]  delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil]; [alert show]; }
#else
#   define ULog(...)
#endif
#define ITTDEBUG
#define ITTLOGLEVEL_INFO     10
#define ITTLOGLEVEL_WARNING  3
#define ITTLOGLEVEL_ERROR    1

#ifndef ITTMAXLOGLEVEL

#ifdef DEBUG
#define ITTMAXLOGLEVEL ITTLOGLEVEL_INFO
#else
#define ITTMAXLOGLEVEL ITTLOGLEVEL_ERROR
#endif

#endif

// The general purpose logger. This ignores logging levels.
#ifdef ITTDEBUG
#define ITTDPRINT(xx, ...)  NSLog(@"%s(%d): " xx, __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#else
#define ITTDPRINT(xx, ...)  ((void)0)
#endif

// Prints the current method's name.
#define ITTDPRINTMETHODNAME() ITTDPRINT(@"%s", __PRETTY_FUNCTION__)

// Log-level based logging macros.
#if ITTLOGLEVEL_ERROR <= ITTMAXLOGLEVEL
#define ITTDERROR(xx, ...)  ITTDPRINT(xx, ##__VA_ARGS__)
#else
#define ITTDERROR(xx, ...)  ((void)0)
#endif

#if ITTLOGLEVEL_WARNING <= ITTMAXLOGLEVEL
#define ITTDWARNING(xx, ...)  ITTDPRINT(xx, ##__VA_ARGS__)
#else
#define ITTDWARNING(xx, ...)  ((void)0)
#endif

#if ITTLOGLEVEL_INFO <= ITTMAXLOGLEVEL
#define ITTDINFO(xx, ...)  ITTDPRINT(xx, ##__VA_ARGS__)
#else
#define ITTDINFO(xx, ...)  ((void)0)
#endif

#ifdef ITTDEBUG
#define ITTDCONDITIONLOG(condition, xx, ...) { if ((condition)) { \
ITTDPRINT(xx, ##__VA_ARGS__); \
} \
} ((void)0)
#else
#define ITTDCONDITIONLOG(condition, xx, ...) ((void)0)
#endif

#define ITTAssert(condition, ...)                                       \
do {                                                                      \
if (!(condition)) {                                                     \
[[NSAssertionHandler currentHandler]                                  \
handleFailureInFunction:[NSString stringWithUTF8String:__PRETTY_FUNCTION__] \
file:[NSString stringWithUTF8String:__FILE__]  \
lineNumber:__LINE__                                  \
description:__VA_ARGS__];                             \
}                                                                       \
} while(0)

系统宏

//获取版本
#define IOS_VERSION [[[UIDevice currentDevice] systemVersion] floatValue]
#define CurrentSystemVersion [[UIDevice currentDevice] systemVersion]
//获取当前语言
#define CurrentLanguage ([[NSLocale preferredLanguages] objectAtIndex:0])
//判断是真机还是模拟器  
#if TARGET_OS_IPHONE  
//iPhone Device  
#endif  
  
#if TARGET_IPHONE_SIMULATOR  
//iPhone Simulator  
#endif
//检查系统版本
#define SYSTEM_VERSION_EQUAL_TO(v)                  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)
#define SYSTEM_VERSION_GREATER_THAN(v)              ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending)
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v)  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN(v)                 ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v)     ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending)

内存宏

//使用ARC和不使用ARC
#if __has_feature(objc_arc)
//compiling with ARC
#else
// compiling without ARC
#endif

#pragma mark - common functions
#define RELEASE_SAFELY(__POINTER) { [__POINTER release]; __POINTER = nil; }

//释放一个对象
#define SAFE_DELETE(P) if(P) { [P release], P = nil; }

#define SAFE_RELEASE(x) [x release];x=nil

图片宏

//读取本地图片
#define LOADIMAGE(file,ext) [UIImage imageWithContentsOfFile:[[NSBundle mainBundle]pathForResource:file ofType:ext]]

//定义UIImage对象
#define IMAGE(A) [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:A ofType:nil]]

//定义UIImage对象
//#define ImageNamed(_pointer) [UIImage imageNamed:[UIUtil imageName:_pointer]]

//前两种宏性能高,省内存
//第三个没有必要使用,因为我们可以使用Xcode的插件

颜色宏

// rgb颜色转换(16进制->10进制)
#define UIColorFromRGB(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0]

//带有RGBA的颜色设置
#define COLOR(R, G, B, A) [UIColor colorWithRed:R/255.0 green:G/255.0 blue:B/255.0 alpha:A]

// 获取RGB颜色
#define RGBA(r,g,b,a) [UIColor colorWithRed:r/255.0f green:g/255.0f blue:b/255.0f alpha:a]
#define RGB(r,g,b) RGBA(r,g,b,1.0f)

//背景色
#define BACKGROUND_COLOR [UIColor colorWithRed:242.0/255.0 green:236.0/255.0 blue:231.0/255.0 alpha:1.0]

//清除背景色
#define CLEARCOLOR [UIColor clearColor]

#pragma mark - color functions
#define RGBCOLOR(r,g,b) [UIColor colorWithRed:(r)/255.0f green:(g)/255.0f blue:(b)/255.0f alpha:1]
#define RGBACOLOR(r,g,b,a) [UIColor colorWithRed:(r)/255.0f green:(g)/255.0f blue:(b)/255.0f alpha:(a)]

其他宏

//方正黑体简体字体定义
#define FONT(F) [UIFont fontWithName:@"FZHTJW--GB1-0" size:F]

//定义一个API
#define APIURL                @"http://xxxxx/"
//登陆API
#define APILogin              [APIURL stringByAppendingString:@"Login"]

//设置View的tag属性
#define VIEWWITHTAG(_OBJECT, _TAG)    [_OBJECT viewWithTag : _TAG]
//程序的本地化,引用国际化的文件
#define MyLocal(x, ...) NSLocalizedString(x, nil)

//G-C-D
#define BACK(block) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block)
#define MAIN(block) dispatch_async(dispatch_get_main_queue(),block)

//NSUserDefaults 实例化
#define USER_DEFAULT [NSUserDefaults standardUserDefaults]


//由角度获取弧度 有弧度获取角度
#define degreesToRadian(x) (M_PI * (x) / 180.0)
#define radianToDegrees(radian) (radian*180.0)/(M_PI)

单例宏

#define SYNTHESIZE_SINGLETON_FOR_CLASS(classname) \
\
static classname *shared##classname = nil; \
\
+ (classname *)shared##classname \
{ \
@synchronized(self) \
{ \
if (shared##classname == nil) \
{ \
shared##classname = [[self alloc] init]; \
} \
} \
\
return shared##classname; \
} \
\
+ (id)allocWithZone:(NSZone *)zone \
{ \
@synchronized(self) \
{ \
if (shared##classname == nil) \
{ \
shared##classname = [super allocWithZone:zone]; \
return shared##classname; \
} \
} \
\
return nil; \
} \
\
- (id)copyWithZone:(NSZone *)zone \
{ \
return self; \
}
查看原文

赞 4 收藏 53 评论 2

ACChe 赞了文章 · 2015-09-23

细说 iOS 消息推送

经常有同学问我们,iOS上推送究竟怎么做啊,为什么我的设备总收不到推送呢,这里跟大家集中讨论一下iOS上推送的实现细节。

APNS的推送机制

与Android上我们自己实现的推送服务不一样,Apple对设备的控制非常严格,消息推送的流程必须要经过APNs:
remote_notif_simple_2x

这里 Provider 是指某个应用的Developer,当然如果开发者使用AVOS Cloud的服务,把发送消息的请求委托给我们,那么这里的Provider就是AVOS Cloud的推送服务程序了。上图可以分为三步:
第一步:AVOS Cloud推送服务程序把要发送的消息、目的设备的唯一标识打包,发给APNs。
第二步:APNs在自身的已注册Push服务的应用列表中,查找有相应标识的设备,并把消息发送到设备。
第三步:iOS系统把发来的消息传递给相应的应用程序,并且按照设定弹出Push通知

为了实现消息推送,有两点非常重要:
1,App的推送证书
要能够完整实现一条消息推送,需要我们在App ID中打开Push Notifications,需要我们准备好Provisioning Profile和SSL证书,并且一定要注意Development和Distribution环境是需要分开的。最后,把SSL证书导入到AVOS Cloud平台,就可以尝试远程消息推送了。具体的操作流程可以参考我们的使用指南:iOS推送证书设置指南
2,设备标识DeviceToken
知道了谁要推送,或者说要推送给哪个App之后,APNs还需要知道推到哪台设备上,这就是设备标识的作用。获取设备标识的流程如下:

第一步:App打开推送开关,用户要确认TA希望获得该App的推送消息
第二步:App获得一个DeviceToken
第三步:App将DeviceToken保存起来,这里就是通过[AVInstallation saveInBackground]将DeviceToken保存到AVOS Cloud
第四步:当某些特定事件发生,开发者委托AVOS Cloud来发送推送消息,这时候AVOS Cloud的推送服务器就会给APNs发送一则推送消息,APNs最后消息送到用户设备

推送相关的几个概念

消息类型

一条消息推送过来,可以有如下几种表现形式:

  • 显示一个alert或者banner,展现具体内容

  • 在应用icon上提示一个新到消息数

  • 播放一段声音

  • 开发者可以在每次推送的时候设置,在推送达到用户设备时开发者也可以选择不同的提示方式。

    本地消息通知

    iOS上有两种消息通知,一种是本地消息(Local Notification),一种是远程消息(Push Notification,也叫Remote Notification),设计这两种通知的目的都是为了提醒用户,现在有些什么新鲜的事情发生了,吸引用户重新打开应用。
    本地消息什么时候有用呢?譬如你正在做一个To-do的工具类应用,对于用户加入的每一个事项,都会有一个完成的时间点,用户可以要求这个To-do应用在事项过期之前的某一个时间点提醒一下TA。为了达到这一目的,App就可以调度一个本地通知,在时间点到了之后发出一个Alert消息或者其他提示。
    我们在处理推送消息的时候,也可以综合运用这两种方式。

    代码里面如何实现推送

    首先,我们要获取DeviceToken。

    App需要每次启动的时候都去注册远程通知——通过调用UIApplication的registerForRemoteNotificationTypes:方法,传递给它你希望支持的消息类型参数即可,例如:

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        // do some initiale working
        ...
        
        [application registerForRemoteNotificationTypes:UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound];
        return YES;
    }
    

    如果注册成功,APNs会返回给你设备的token,iOS系统会把它传递给app delegate代理——application:didRegisterForRemoteNotificationsWithDeviceToken:方法,你应该在这个方法里面把token保存到AVOS Cloud后台,例如:

    - (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
        NSLog(@"Receive DeviceToken: %@", deviceToken);
        AVInstallation *currentInstallation = [AVInstallation currentInstallation];
        [currentInstallation setDeviceTokenFromData:deviceToken];
        [currentInstallation saveInBackground];
    }
    

    如果注册失败,application:didFailToRegisterForRemoteNotificationsWithError:方法会被调用,通过NSError参数你可以看到具体的出错信息,例如:

    - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
        NSLog(@"注册失败,无法获取设备ID, 具体错误: %@", error);
    }
    

    请注意,注册流程需要在app每次启动时调用,这并不不会带来额外的负担,因为iOS操作系统在第一次获得了有效的device token之后,会本地缓存起来,以后app再调用registerForRemoteNotificationTypes:的时候会立刻返回,并不会再进行网络请求。另外,app层面不应该对device token进行缓存,因为device token也有可能变化——如果用户重装了操作系统,那么APNs再次给出的device token就会和之前的不一样,又或者是,用户restore了原来的backup到新的设备上,那么原来的device token也会失效。

    其次,我们要处理收到消息之后的回调

    我们可以设想一下消息通知的几种使用场景:
    1,在app没有被启动的时候,接收到了消息通知。这时候操作系统会按照默认的方式来展现一个alert消息,在app icon上标记一个数字,甚至播放一段声音。
    2,用户看到消息之后,点击了一下action按钮或者点击了应用图标
    如果action按钮被点击了,系统会通过调用application:didFinishLaunchingWithOptions:这个代理方法来启动应用,并且会把notification的payload数据传递进去。
    如果应用图标被点击了,系统也一样会调用application:didFinishLaunchingWithOptions:这个代理方法来启动应用,唯一不同的是这时候启动参数里面不会有任何notification的信息。
    示例代码如下:

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        // do initializing works
        ...
        
        if (launchOptions) {
            // do something else
            ...
        
            [AVAnalytics trackAppOpenedWithLaunchOptions:launchOptions];
        }
        
        [application registerForRemoteNotificationTypes:UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound];
    
        return YES;
    }
    

    3,如果远程消息发送过来的时候,app正在运行,这时候会发生什么呢?
    app代理的application:didReceiveRemoteNotification:方法会被调用,同时远程消息中的payload数据会作为参数传递进去。
    示例代码如下:

    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
        if (application.applicationState == UIApplicationStateActive) {
            // 转换成一个本地通知,显示到通知栏,你也可以直接显示出一个alertView,只是那样稍显aggressive:)
            UILocalNotification *localNotification = [[UILocalNotification alloc] init];
            localNotification.userInfo = userInfo;
            localNotification.soundName = UILocalNotificationDefaultSoundName;
            localNotification.alertBody = [[userInfo objectForKey:@"aps"] objectForKey:@"alert"];
            localNotification.fireDate = [NSDate date];
            [[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
        } else {
            [AVAnalytics trackAppOpenedWithRemoteNotificationPayload:userInfo];
        }
    }
    

    常见问题FAQ

    我能推送长消息吗

    不能,APNs限制了每个notification的payload最大长度是256字节,超长的消息是不能发送的。

    推送怎么加声音提醒

    消息推送是可以指定声音的。譬如你可以对正面的反馈使用欢快的声音,对负面的反馈使用低沉一点的声音,都可以达到别出心裁让人眼前一亮的目的。
    你需要先放一些aiff、wav或者caf音频文件到app的资源文件中,然后在推送的时候指定不同的音频文件名就可以了。

    推送的Badge是怎么回事

    推送并不一定会导致应用图标上红色数字增加,是否显示这一数字,显示成多少,都取决于开发者自己。
    在发送推送消息的时候,我们可以选择是否递增这一数字;如果不选择这一项,那么消息推送并不会导致应用图标上红色数字的出现。

    好,现在问题来了,这个数字如果搞出来了,怎么让它消失掉呢?
    其实我们只需要在任何时候设置 UIApplication.applicationIconBadgeNumber 属性为0,就可以让这个数字消失掉。
    一般我们会选择在应用启动的时候(application:didFinishLaunchingWithOptions:方法中),或者干脆一点,在应用每次被切换到前台的时候(applicationWillEnterForeground:方法中),调用这一行代码,即可立刻清除掉Badge数字了。

    AVOS Cloud平台发出去的通知格式究竟是什么样子的

    对于每一条推送消息,都包含一个payload,通常是组成了一个JSON的Dictionary,这其中必不可少的是aps属性,它对应的value也是一个Dictionary,包含下面一些内容:

  • alert消息(文本或Dictionary)

  • 应用图标上的红色数字

  • 播放的声音文件名

  • 在由推送激活的app打开事件中,application:didFinishLaunchingWithOptions:的options参数就是这个大的Dictionary对象。
    {
        aps =     {
            alert = "hello, everyone";
            badge = 2;
            sound = default;
        };
    }
    

    这里要注意的时alert部分,它的值可以是一个String(文本消息),也可以是一个JSON的Dictionary。当它是文本消息的时候,系统就会把这些文字显示到一个alertview中;如果它也是由一个JSON Dictionary组成的话,其格式如下:

  • body

  • action-loc-key

  • loc-key

  • loc-args

  • launch-image

  • body部分就是alertView中将要展现出来的文本消息,loc-属性主要是用来实现本地化消息,launch-image只是app主bundle里的一个图片文件的名称,一般来说我们不指定这一属性。

    如何显示本地化的消息

    有两种办法可以实现推送消息的本地化:
    1,在推送的payload中使用loc-key和loc-args来指定进行本地化,这样Provider方只需要按照统一的格式来发送即可,消息的解析和组装则由客户端来完成。
    2,如果推送的payload里面不包含loc-key/loc-args信息,那么Provider方就需要自己做本地化处理,然后给不同的device发送不同的消息,为了做到这一点,还需要app在上传device token的时候也把用户的语言设置信息传回来。
    目前,因为AVOS Cloud主要就是瞄准中国大陆市场和海外中文用户,所以我们在推送上还不提供多语言支持。

    应用该怎么响应推送消息

    上面说的处理流程,只能简单展示一下远程消息,激活用户让他们重新回到app中来。但是有时候,我们希望带给用户更好的使用体验,譬如如果我们告诉用户:张三刚刚评论了你的照片。这时候用户如果点击action按钮进入app,我们是展示具体的评论页面为好,还是展示通常的启动页面然后让用户自己去找张三的评论好?我想负责任的开发者都会选择前者:)

    要做到灵活响应不同类型的通知消息,我们需要在通知的payload中增加更多信息,而不能仅仅只有alert出来的文字信息。对于AVOS Cloud消息推送平台来讲,就需要开发者使用更高级功能的JSON格式。譬如我们发送这样的json字符串
    {"action":{"type":4},"alert":"hello, everyone”} 最终在app内会收到这样的UserInfo Dictionary:

    {
        action =     {
            type = 4;
        };
        aps =     {
            alert = "hello, everyone";
            badge = 4;
        };
    }
    

    “hello, everyone”会显示到alertView中,但是整个Dictionary会通过launchOptions传递给application: didFinishLaunchingWithOptions: 方法,这样我们在程序里面就可以对不同的消息实现不同的跳转了。

    查看原文

    赞 2 收藏 62 评论 3

    ACChe 关注了问题 · 2015-09-15

    解决UITableViewStylePlain UITableView 如何不显示分割线

    我的UITableView是UITableViewStylePlain风格的,这样整个TableView都会被分割线分隔开,不管有没有数据,非常丑。。。
    比如下图,我只有4行数据

    -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
        return 4;
    }

    UITableView
    找了好久没有找到相关的选项,让有数据的Cell才显示分割线,没有数据的不显示。
    求解一个方案。。。

    关注 3 回答 7

    ACChe 关注了问题 · 2015-09-14

    解决iOS中本地json数据如何更新?

    比如,之前我在本地已经存储了一个用于UITableView的json文件,在下次需要更新这条数据时,是怎么的思路处理?
    我暂时想到的:(1)本地数据与重新获得数据的匹配,不同则把旧文件删除,将新的文件存储起来,这样需要重新加载UITableView(2)匹配之后直接在旧文件后面添加新的数据,但是这里如何添加,添加几条都不好操作。

    所以,想问一下,一般在手机本地的结构比较复杂的JSON数据如何获取更新,思路是怎样的?

    关注 3 回答 1

    ACChe 赞了问题 · 2015-09-02

    解决UITableViewCell 如何获取自身高度

    如题,我在代码中构建cell,在UITableView的delegate中用heightForRow的delegate方法来确定cell高度。

    那么如何在cell中获取这个高度来进行里面的内容的布局呢

    关注 3 回答 2

    ACChe 关注了问题 · 2015-08-18

    解决如何使用iOS原生地图,显示公交路线

    有懂地图导航的没?ios原生地图开发只支持步行和自驾,为什么美团和大众点评都支持公交路线呢?我看了下他们地图也是原生的呀

    关注 3 回答 2

    ACChe 关注了问题 · 2015-08-18

    解决app的电影选座功能是怎么做的呢(iOS版),已经解决!

    app的电影选座功能是怎么做的呢(iOS版,已经上传至github,感谢各位网友的热心帮助哈!)
    图片描述

    解决方法:现在这个版本还存在一些bug,请大家包含哈!
    https://github.com/mrhyh/selectSeat

    关注 9 回答 4