摘要:對蘋果開發(fā)者來講,2014年是令人難以置信的一年。在這短短的一年中發(fā)生了如此多的變化:在充滿吸引力的Swift面前,我們幾乎忘了之前是如何癡迷O(shè)C;以及充滿想象力的iOS8和WatchKit,難以想象還有什么API能與之相比。
回顧過去一年發(fā)生在我們身邊的事情時,有一點不得不提:對蘋果開發(fā)者來講,2014年是令人難以置信的一年。在這短短的一年中(有關(guān)APP的開發(fā))發(fā)生了如此多的變化:在充滿吸引力的Swift面前,我們幾乎忘了之前是如何癡迷于Objective-C;以及充滿想象力的iOS 8和WatchKit,難以想象還有什么API能與之相比。
NSHipster的慣例:請可愛的童鞋們,在新年的天,為大家展示你們(在開發(fā)中)常使用的技巧和方法。如今,隨著來自Cupertino(蘋果總部,位于舊金山)和眾多開源社區(qū)的一系列API的涌現(xiàn),媽媽再也不用擔心我們找不到有趣的東西來分享啦!
在此,感謝以下童鞋們所做的貢獻:
Colin Rofls、Cédric Luthi、Florent Pillet、Heath Borders、Joe Zobkiw、Jon Friskics、Justin Miller、Marcin Matczuk、Mikael Konradsson、Nolan O'Brien、Robert Widmann、Sachin Palewar、Samuel Defago、Sebastian Wittenkamp、Vadim Shpakovski、Zak。
成員函數(shù)的使用技巧(來自Robert Widmann)
在用靜態(tài)方式調(diào)用Swift類和結(jié)構(gòu)中的成員函數(shù)時,通常使用以下格式:
Object->(參數(shù))->Things
比如,你可以用以下兩種方式調(diào)用reverse():
[cpp] view plaincopy
[1,2,3,4].reverse( )
Array.reverse([1,2,3,4])
用@()來封裝C字符串(來自Samuel Defago)
事實上文字大部分時候是數(shù)字和字母的集合,使用C字符串,尤其當我在使用運行時編碼的時候,我常常會忘記用UTF8編碼、以NULL結(jié)束:Objective-C字符串封裝:
[cpp] view plaincopy
NSString *propertyAttributesString =
@(property_getAttributes(class_getProperty([NSObject class], "description")));
// T@"NSString",R,C
AmIBeingDebugged
Nolan O'Brien在這篇Q&A技術(shù)文檔中讓我們注意到了AmIBeingDebugged函數(shù)方法:
[cpp] view plaincopy
#include <assert.h>
#include <stdbool.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/sysctl.h>
static Bool AmIBeingDebugged(void) {
int mib[4];
struct kinfo_proc info;
size_t size = sizeof(info);
info.kp_proc.p_flag = 0;
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PID;
mib[3] = getpid();
sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
return (info.kp_proc.p_flag & P_TRACED) != 0;
}
使用延遲存儲屬性(來自Colin Rofls)
在開發(fā)過程中,應(yīng)該避免使用Optionals類型,更不應(yīng)該使用隱式解包optionals類型。你想聲明一個var變量卻不想給一個初始值?使用“l(fā)azy”吧,要注意的就是:在你的屬性被賦值之前不要調(diào)用getter方法即可(童叟無欺!)
[cpp] view plaincopy
lazy var someModelStructure = ExpensiveClass()
假如你僅僅對這var變量調(diào)用set方法,而沒有調(diào)用getter方法的話,這個被lazy修飾的var變量不會被賦值。例如,用lazy修飾那些直到viewDidLoad時才需要初始化的views變量就會非常合適。
獲取Storyboard視圖容器里的子視圖控制器(來自Vadim Shpakovski)
有一個比較方便的方法來獲取故事板視圖容器里的子視圖控制器:
[cpp] view plaincopy
// 1. A property has the same name as a segue identifier in XIB
@property (nonatomic) ChildViewController1 *childController1;
@property (nonatomic) ChildViewController2 *childController2;
// #pragma mark - UIViewController
- (void)prepareForSegue:(UIStoryboardSegue *)segue
sender:(id)sender
{
[super prepareForSegue:segue sender:sender];
// 2. All known destination controllers assigned to properties
if ([self respondsToSelector:NSSelectorFromString(segue.identifier)]) {
[self setValue:segue.destinationViewController forKey:segue.identifier];
}
}
- (void)viewDidLoad {
[super viewDidLoad];
// 3. Controllers already available bc viewDidLoad is called after prepareForSegue
self.childController1.view.backgroundColor = [UIColor redColor];
self.childController2.view.backgroundColor = [UIColor blueColor];
}
重復(fù)運行項目,不重復(fù)構(gòu)建項目(來自Heath Borders)
假如你一直在不停地調(diào)試同一個問題,你可以在不重復(fù)構(gòu)建的情況下運行你的APP,這樣:“Product>Perform Action>Run without Building”
快速獲取Playground資源(來自Jon Friskics)
Swift里的所有Playground共享相同的數(shù)據(jù)目錄:/Users/HOME/Documents/Shared Playground Data
如果你喜歡使用很多Playgrounds,你將需要在上述共享目錄下為每個Playground新建對應(yīng)的子目錄,來存儲每個Playground用到的數(shù)據(jù);但是那之后你需要告訴每個Playground在哪兒可以獲取其對應(yīng)的數(shù)據(jù)。下面是我常用的一個輔助解決方法:
[cpp] view plaincopy
func pathToFileInSharedSubfolder(file: String) -> String {
return XCPSharedDataDirectoryPath + "/" + NSProcessInfo.processInfo().processName + "/" + file
}
processName屬性是Playground文件的名字,因此只要你已經(jīng)在Playground數(shù)據(jù)共享文件目錄下以相同的名字新建了一個子目錄,那么你可以很容易訪問這些數(shù)據(jù),和讀取本地JSON數(shù)據(jù)一樣:
[cpp] view plaincopy
var jsonReadError:NSError?
let jsonData = NSFileManager.defaultManager().contentsAtPath(pathToFileInSharedSubfolder("data.json"))!
let jsonArray = NSJSONSerialization.JSONObjectWithData(jsonData, options: nil, error: &jsonReadError) as [AnyObject]
....或者訪問本地圖片
[cpp] view plaincopy
let imageView = UIImageView()
imageView.image = UIImage(contentsOfFile: pathToFileInSharedSubfolder("image.png"))
Please attention!本篇文章剩余的部分來自Cédric Luthi大神的貢獻,他分享了一些比較有用的開發(fā)技巧和技術(shù),這些內(nèi)容足夠自成一篇,值得細細品讀。這里再次感謝Cédric!
CocoaPods大揭秘
這兒有一個快速的方法來檢查APP里用到的所有pods:
[cpp] view plaincopy
$ class-dump -C Pods_ /Applications/Squire.app | grep -o "Pods_\w+"
CREATE_INFOPLIST_SECTION_IN_BINARY
注意Xcode中為命令模式APP(command-line apps)設(shè)置的CREATE_INFOLIST_SECTION_IN_BINARY屬性。這比使用-sectcreate__TEXT__info_plist鏈接標志位更加容易,前者還把已經(jīng)編譯好的Info.plist文件嵌入在二進制編碼中。
關(guān)于如何向蘋果提需求,它也給我們上了一課,這個特性需求早在2006年的 rdar://4722772 被提出,但直到7年后才被滿足。
(譯者注:言外之意是它是反面教材,應(yīng)該更有技巧的提需求)
禁用dylib鉤子(來自Sam Marshall)
Sam Marshall這個技巧可謂是走自己的路,讓黑客無路可走。
在你的“Other Linker Flags”里加上下面這行:
[cpp] view plaincopy
-Wl,-sectcreate,__RESTRICT,__restrict,/dev/null
NSBundle -preferredLocalizations
某些時候,你需要知道APP當前使用的是什么語言。通常,大家會使用NSLocal+preferredLanguages. 可惜的是這個方法不會告訴你APP實際呈現(xiàn)的文字語種。你僅僅會得到iOS系統(tǒng)里“Settings->General->Language&Region->Preferred Language”列表中的選項,或者OSX系統(tǒng)里“System Preferences->Language & Region->Preferred Languages”列表中的選項。想象一下:優(yōu)先語言列表中只有{英語,法語},但你的APP僅使用德語;調(diào)用[[NSLocal preferredLanguages] firstObject]返回給你的是英語,而不是德語。
正確的方法是用[[NSBundle mainBundle] preferredLocalizations]方法。
蘋果的開發(fā)文檔是這樣說的:
一個包含了在bundle中本地化的語言ID的NSString對象的數(shù)組,里面的字符串排序是根據(jù)用戶的語言偏好設(shè)置和可使用的地理位置而來的。
NSBundle.h里的備注:
一個bundle中本地化的子集,重新排序到當前執(zhí)行壞境的優(yōu)先序列里,main bundle的語言順序中前面的是用戶希望在UI界面上看到的語種。
當然你也許需要調(diào)用這個方法:
[cpp] view plaincopy
NSLocal+canonicalLanguageIdentifierFromString:
來確保你使用的文字語種是規(guī)范的語種。
保護SDK頭文件
如果你用dmg安裝Xcode,那么看看這篇Joar Wingfors的文章,它講述了如何通過保留所有權(quán)來避免SDK頭文件被意外修改:
[cpp] view plaincopy
$ sudo ditto /Volumes/Xcode/Xcode.app /Applications/Xcode.app
任意類型的實例變量檢測
為了達到逆向處理的目的,查詢對象的實例變量是一個常見可靠的途徑。通常調(diào)用對象valueForKey:方法就能達到這一目的,除了少數(shù)重寫了類方法+accessInstanceVariablesDirectly的類屏蔽了該操作。
下面是一個例子:當實例變量有一個為任意類型的屬性時,上述提到的操作無效
這是iOS6.1 SDK中MediaPlayer 框架的一段引用:
[cpp] view plaincopy
@interface MPMoviePlayerController : NSObject {
void *_internal; // 4 = 0x4
BOOL _readyForDisplay; // 8 = 0x8
}
因為 id internal=[moviePlayerController valueForKey:@”internal”] 無效,下面有一個笨辦法來取得這個變量:
[cpp] view plaincopy
id internal = *((const id*)(void*)((uintptr_t)moviePlayerController + sizeof(Class)));
注意!不要隨意調(diào)用這段代碼,因為ivar的布局可能改變(指針偏移量計算可能出錯)。僅在逆向工程中使用!
NSDateFormatter +dateFormatFromTemplate:options:locale:
友情提示:假如你調(diào)用[NSDateFormatter setDateFormat],而沒有調(diào)用[NSDateFormatter dateFormatFromTemplate:options:local:],n那么很可能出錯。
蘋果文檔:
[cpp] view plaincopy
+ (NSString *)dateFormatFromTemplate:(NSString *)template
options:(NSUInteger)opts
locale:(NSLocale *)locale
不同地區(qū)有不同的日期格式。使用這個方法的目的:得到指定地區(qū)指定日期字段的一個合適的格式(通常你可以通過currentLocal查看當前所屬地區(qū))
下面這個例子給我們表現(xiàn)了英式英語和美式英語不同的日期格式:
[cpp] view plaincopy
NSLocale *usLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
NSLocale *gbLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_GB"];
NSString *dateFormat;
NSString *dateComponents = @"yMMMMd";
dateFormat = [NSDateFormatter dateFormatFromTemplate:dateComponents options:0 locale:usLocale];
NSLog(@"Date format for %@: %@",
[usLocale displayNameForKey:NSLocaleIdentifier value:[usLocale localeIdentifier]], dateFormat);
dateFormat = [NSDateFormatter dateFormatFromTemplate:dateComponents options:0 locale:gbLocale];
NSLog(@"Date format for %@: %@",
[gbLocale displayNameForKey:NSLocaleIdentifier value:[gbLocale localeIdentifier]], dateFormat);
// Output:
// Date format for English (United States): MMMM d, y
// Date format for English (United Kingdom): d MMMM y
通過調(diào)試獲取內(nèi)部常量
近期,Matthias Tretter在Twitter上問到:
有人知道在iOS8里modal viewController presentation的默認動畫時間和跳轉(zhuǎn)方式嗎?
我們在UIKit的類庫中發(fā)現(xiàn)了這樣一個函數(shù):[UITransitionView defaultDurationForTransition:],并在這個方法的位置加一個斷點:
[cpp] view plaincopy
(lldb) br set -n "+[UITransitionView defaultDurationForTransition:]"
模態(tài)顯示一個viewController,就會停在這個斷點,輸入finish執(zhí)行該方法:
[cpp] view plaincopy
(lldb)finish
在defaultDurationForTransition:被執(zhí)行時,你就能讀到結(jié)果(在xmm0寄存器里)
[cpp] view plaincopy
(lldb) register read xmm0 --format float64
xmm0 = {0.4 0}
回復(fù):默認動畫時間0.4s
DIY 弱關(guān)聯(lián)對象
不幸的是,關(guān)聯(lián)對象OBJC_ASSOCIATION_ASSIGN策略不支持引用計數(shù)為0的弱引用。幸運的是,你可以很容易實現(xiàn)它,你僅僅需要一個簡單的類,并在這個類里弱引用一個對象:
[cpp] view plaincopy
@interface WeakObjectContainter : NSObject
@property (nonatomic, readonly, weak) id object;
@end
@implementation WeakObjectContainter
- (instancetype)initWithObject:(id)object {
self = [super init];
if (!self) {
return nil;
}
self.object = object;
return self;
}
@end
然后,通過OBJC_ASSOCIATION_RETAIN(_NONATOMIC)關(guān)聯(lián)WeakObjectContainter:
[cpp] view plaincopy
objc_setAssociatedObject(self, &MyKey, [[WeakObjectContainter alloc] initWithObject:object], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
用object屬性指向這個所需的引用計數(shù)為0的弱引用對象。
[cpp] view plaincopy
id object = [objc_getAssociatedObject(self, &MyKey) object];
英文文章來源:NSHipster,譯文出自:CocoaChina
本站文章版權(quán)歸原作者及原出處所有 。內(nèi)容為作者個人觀點, 并不代表本站贊同其觀點和對其真實性負責,本站只提供參考并不構(gòu)成任何投資及應(yīng)用建議。本站是一個個人學(xué)習(xí)交流的平臺,網(wǎng)站上部分文章為轉(zhuǎn)載,并不用于任何商業(yè)目的,我們已經(jīng)盡可能的對作者和來源進行了通告,但是能力有限或疏忽,造成漏登,請及時聯(lián)系我們,我們將根據(jù)著作權(quán)人的要求,立即更正或者刪除有關(guān)內(nèi)容。本站擁有對此聲明的最終解釋權(quán)。