OC方面的基礎筆記:
1.類的基本用法
#import <Foundation/Foundation.h>
// 大體上就是include, 用於包含頭文件, 但是即使頭文件中, 沒有ifndef defined endif, 仍然能夠踢除重複包含的頭文件
// ----@interface section----
// OC中聲明和實現是分離的, 兩個都必須有.
@interface Fraction : NSObject { // @interface 類名: 父類名字
// 在這裏聲明成員變量, 在OC中稱爲字段, 描述類的對象的屬性
// 只有成員變量需要在外部提供接口, 或者是需要被繼承的時候, 才需要在.h文件中定義成員變量, 其他情況下只需要在.m文件中, 聲明全局變量即可
int _numerator; // 在類內一律使用成員變量, 在類外使用屬性
int _denominator;
}
@property (nonatomic, assign) int numerator, denominator;
-(void) setNumerator: (int) n; // 聲明方法
-(void) setNumerator: (int) n andDenominator: (int) d;
-(Fraction *) init; // 或者使用-(id) init; OC中只要是以init開頭的都是構造函數, 可以帶參數
@end
// ---@implementation section----方法的實現---實例方法(以-號開頭)----類方法(以+號開頭)
@implementation Fraction // @implementation 類名
@synthesize numerator = _numerator, denominator = _denominator;
-(void) setNumerator: (int) n andDenominator: (int) d;
{
_numerator = n;
_denominator = d;
}
-(Fraction *) init
{
if (self = [super init]){
// super是指向父類的指針, super的值和self一樣, Fraction中不僅有自己的成員變量, 還有父類中的內容, 因此初始化的時候, 要先初始化父類的內容, 然後再初始化自己本身的內容.
_numerator = _denominator = 0;
}
return self; // self是當前對象的地址, 也相當於一個成員變量
}
@end
// ---- program section ----主函數部分
int main (int argc, const char * argv[])
{
@autoreleasepool { // 爲自動釋放池在內存中保留了空間
Fraction *myFraction = [[Fraction alloc] init]; // 聲明一個類的對象,分配內存空間並初始化
// OC不允許類的對象創建在棧裏, 自己寫的類的對象只能創建在堆裏
// [Fraction alloc]這個表達式創建一個Fraction的對象在堆空間,表達式的值是該對象的地址, myFraction只是對象的指針
// 或者使用 Fraction *myFraction = [Fraction new];
[myFraction setNumerator: 1]; // 把消息發送給對象,調用setNumerator方法,傳遞一個參數爲1.
// 調用myFraction指向的對象的方法setNumerator,
// 對象的地址(不是指針)調用對象的方法, (地址是常量, 指針是變量)
// 也叫做給對象發送setNumerator消息
// OC完全兼容C; OC有自己專用的字符串, 同時也兼容C的字符串
NSLog(@"The value of myFraction is: "); // 顯示, @表示NSString型字符串對象, 不加爲普通字符串
}
return 0;
}
2.不可變字符串
OC的字符串是一個對象, 它的類型是NSString類.
OC所以會使用自己專有的字符串, 因爲這個字符串是個對象, 有很多的方法, 比外來函數如strlen等更方便, 更面向對象.
NSString * str = @"Hello World!";
// @"Hello World!"這個表達式表示在只讀數據段裏, 創建了一個NSString的對象, 內容是Hello World!, 表達式的值是該對象的地址, 只有字符串可以這樣創建一個對象
// NSString的對象是不可變的
// NSMutableString的對象是可變的
NSString * str2 = [[NSString alloc] initWithString: str];
NSString * str3 = [[NSString alloc] initWithUTF8String:"HEllo World!"];
//用C的字符串創建OC的字符串, 完成C的字符串轉換成OC的字符串
char * cString = [@"Objective-C String" UTF8String]; // 將OC字符串轉換爲C字符串
NSString * str4 = [[NSString alloc] initWithFormat:@"hello %c %d %f", 'A', 3, 3.14];
// 根據格式符, 拼接創建一個字符串, 最強大
還有類方法[NSString stringWithString: str];
[NSString stringWithUTF8String:"hello"];
[NSString stringWithFormat:@"hello %d", 5];
characterAtIndex
length // 沒有尾0
轉換大小寫:
uppercaseString // 全大寫
lowercaseString // 全小寫
capitalizedString // 單詞首字母大寫
比較大小:
isEqualToString
compare
hasPrefix: // 是否包含前綴
hasSuffix: // 是否包含後綴
查找:
rangeOfString 查找字符串中子串的範圍, range.location range.length
提取子串:
subStringToIndex
subStringFromIndex
subStringWithRange
結構體的對象能存儲在棧裏, 而類的對象不能存儲在棧裏.
3. 可變字符串
NSMutableString : NSString
NSString 的方法 NSMutableString 也能用
傳參的時候可以傳NSString * 也可以傳 NSMutableString *
setString 設置或替換當前字符串內容
增:
追加: appendString appendFormat
插入: insertString: atIndex:
刪:
deleteCharactersInRange
生成range的函數NSMakeRange(2, 4);
改:
replaceCharactersInRange: withString:
查:
rangeOfString
4.類別
只有OC纔有類別 (categoryName)
類別就是類的升級補丁, 可以被繼承
類別不能用來添加成員變量, 只能添加方法(包括類方法和成員方法)
5.不可變數組
數組的元素是任意的對象, 不僅僅侷限於字符串, 數組中只裝了對象的地址. 相當於指針數組.
和C中的數組不同, 元素可以是不同類型的對象, 在結構上講, 它是一個鏈表.
initWithObjects
使用%@打印的都是對象, 而且這個類都要有一個description方法
-(NSString *) description; // description方法只能這樣寫, 打印的是該方法的返回值, 對中文支持不好
直接遍歷:
NSLog(@"%@", arrayName);
枚舉法遍歷:
用當前數組創建一個枚舉器(NSEnumerator *)enumerator = [array objectEnumerator], 然後調用枚舉器的nextObject方法, 返回數組中每個元素的地址.
快速枚舉法:
for (id obj in arrayName){}
專門用於枚舉數組的for, 和平常的for不是一個. 每次循環得到一個數組元素的地址.
循環遍歷:
objectAtIndex: // 返回數組中一個元素的地址
indexOfObject: // 某元素的下標
count // 元素個數
[array containsObject: ] // array中是否包含某個元素
componentsJoinedByString: // 將數組中的元素組合起來
componentsSeparatedByString: // 分割字符串
componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString: ] // 使用字符分割字符串
6.可變數組
初始化:
initWithArray
[arrayName ObjectsAtIndexes]
setArray
枚舉法遍歷的時候不允許修改其中的元素及個數和順序, 快速枚舉法也不可以, 但是逆序枚舉的時候reverseObjectEnumerator可以修改
增加:
addObject
insertObject: atIndex:
刪除:
removeObjectAtIndex
removeObject
exchange replace
排序:
sortUsingSelector: @selector(isBigThan:) // selector叫做選擇器, 相當於成員方法的函數指針
7.SEL
SEL是一個類型, 用SEL聲明的一個變量, 裏面裝的是消息, 如: SEL s = @selector(methodName); // 有參數要寫:, 即完整的方法名
@selector實際上返回的是名字ID; 作用是使方法可以賦值, 因此就可以傳參, 即可以作爲函數的參數來使用
編譯器給每一個方法都分配了一個編號(0, 1, …), 叫名字ID, 不同的類中, 只要方法的名字相同, 名字ID就相同, 不同的是每個方法的入口地址不一樣
[objectName performSelector: s withObject: ] // 還原方法, 並調用, 有1個參數, 就加1個withObject, 最多支持2個
repondesToSelector:@selector() //對象能否響應selector指定的方法
8.Class
Class也是一個類型, 是裝類的變量, 如: Class cls = [ClassName class]; 之後就可以用cls代替ClassName
作用是使類可以賦值, 因此就可以傳參, 用於isKindOfClass: [ClassName class] // 是否是ClassName類或其子類
isSubclassOfClass:[ClassName class]
9.字典
NSMutableDictionary: NSDictionary
initWithObjectsAndKeys:
// 字典中的成員稱爲鍵值對, @"One"和@"1"組成一個鍵值對, @"One"稱爲值(Value), @"1"稱爲鍵(Key);
// 鍵和值都是任意對象, 不過, 鍵往往使用字符串, 字典裏裝的只是鍵和值的地址, 值可以重複, 但是鍵不能重複, 爲同一個key設置value時, 將會替換掉原始的value
// 字典中的鍵值對沒有順序, 沒有第一個第二個之說, 和數組不一樣, 結構上也是鏈表
ObjectForKey // 可以迅速通過key來找到一個值(value)
count // 鍵值對的個數
枚舉法遍歷有兩種, 通過鍵枚舉遍歷(keyEnumerator), 通過值枚舉遍歷[objectEnumerator], 快速枚舉法, 遍歷到的是鍵,
setObject: forKey:
removeObjectForKey
10.setter, getter, @property
在setter, getter中, 可以使用.運算符, 如果是賦值, 是使用set方法, 如果是使用私有變量, 是使用get方法
@property (readonly) // 表示只創建get方法, 不創建set方法, 沒有write only
(atomic) // 原子操作, 有這個參數時, 在這個線程運行結束之前, 不允許其他線程使用我已用的資源, 正常情況下, 線程是可以使用同一個資源, 並且多個線程間可以交替的運行
(nonatomic) // 不必原子操作, 默認是原子操作的, 因此有時會加這個參數
@property (getter = OtherName) int name; // 修改默認的getter名字name爲OtherName, 使用的時候可以同時使用這兩個名字
@property (setter = setOther:) // setter後面必須有冒號;
(assign) 和 (readwrite) 都是默認的屬性, 不需要寫, 有時寫上assign, 表示其他屬性都不需要, 而不是忘記寫了其他屬性
// 如:@property NSString * name; 這時會有錯誤, 寫成@property (assign) NSString * name; 就沒有錯誤了
// 多個屬性之間, 使用逗號隔開
(copy) (retain) // 這樣聲明對象的時候, 要在dealloc中加入release.
// NSString使用copy, 其他的對象都用retain, 基本數據類型都是默認的assign
11.繼承
多態: 同一名字的方法, 做不同的事情, 有重載, 重寫, 虛函數
封裝: 把複雜的功能, 封裝成相對簡單的代碼, 如函數, 宏, @property, 結構體, 類
private: 不能被子類繼承, 不能被外部函數訪問, 但是繼承的時候, 子類也給private分配了空間,
protected: 能被子類繼承, 不能被外部函數訪問
public: 可以被子類繼續, 可以被外部函數訪問
C++繼承時的方式也有三種, 但是和變量的權限完全不一樣. private繼承, 繼承來的成員, 都變成私有的; protected繼承, 繼承來的成員, 都變成受保護的; public繼承, 繼承來的成員, 原本是什麼權限, 還是什麼權限.
OC中只有公有繼承, 子類繼承之後, 多了一些變量, 就叫做派生, 子類的成員分爲繼承和派生兩部分.
繼承的時候是完整的繼承了父類的全部, 使用繼承自父類的方法, 可以去訪問父類的私有成員, 雖然子類中沒有父類的私有成員, 但確爲其分配了空間
NSString, NSArray, NSDictionary這三個類不能被程序員自己繼承
虛函數: OC中的所有成員方法都是虛函數,
1) 父類的指針可以指向子類的對象
2) 調用方法時, 不看指針只看對象
不同事物被同一事件觸發, 產生不同的響應
12.
棧: (函數, 結構, 變量等存儲的地方)
堆:
數據段:
只讀數據段:
代碼段: (告訴cpu做什麼, 然後在棧裏面開始做)(函數等結構的入口地址都是在代碼段的, 實體是存儲在棧中的)
壓棧
13.內存管理
就是堆空間的創建和釋放問題, C語言在釋放堆的時候, 有不足, 因此OC有自己的內存管理
給一個對象分配一個堆, 只是將這處堆空間設爲私有的, 將這處對象釋放後, 將這處堆空間設爲公有的, 但堆中存儲的內容還存在, 沒有丟失, 除非之後再給這個堆空間覆蓋(重新賦值).
C語言中不釋放會發生內存泄露, 釋放兩次, 會出現重複釋放, free(p)的時候, 釋放的是p指向的堆空間, C中也有類似於OC的計數器, 叫PV操作(加減操作), 不過需要自己寫計數器, 和釋放函數
alloc時, 自動將計數器設爲1, retain計數器加1, release減1, retainCount查看引用計數
內存管理黃金法則:
1.(公認) 當使用alloc, retain, copy, mutableCopy, new "創建"一個對象, 或增添一個指針, 則必須使用release或autorelease進行"釋放".
2.(非公認) 每個指針做自己的內存管理, 每個類做自己的內存管理, 各人顧各人.
放在只讀數據段中的對象, 計數器被設置爲負數(-1), retain遇見負數的時候, 什麼也不會做, 不修改計數器, 因爲只讀數據段不能被修改
-(void)dealloc; 析構方法, 沒有參數, 不能重載
常量字符串的set方法, if (name != newName){ [name release]; name = [newName retain];}, 之後還要在dealloc中加入[name release]; [super dealloc];
autorelease, 將使用autorelease的對象放入最近的自動釋放池中, 等池釋放的時候, 才釋放對象; 原則上, 除非萬不得已, 不要使用autorelease. 在類方法中, 一般都是使用autorelease; 在get對象時, 最好也要使用, return [[name retain] autorelease];
IOS系統下, 每個觸發週期, 都會創建並釋放一個自動釋放池
還有一種就是ARC(自動管理內存)
14.協議
協議是完成兩個類之間通信的一種機制, 在兩個類的對象之間傳遞信息.
發送方持有協議, 接收方遵守協議.
@protocol <protocolName> id <protocolName> delegateName;
@required // 遵守協議的類必須實現該方法, 默認的屬性
@optional // 可選的
協議中聲明過的方法, 遵從協議的類可以不寫聲明, 直接寫實現
如果兩個對象互爲代理, 即互爲引用, 若都計數, 會發生死鎖; 所以當兩個對象互爲代理的時候, 若A->p = [B retain],(A對B強引用), 那麼B->p = A,(B對A弱引用) 不要再計數.
單向協議的時候, 如果協議沒有單獨放在一個文件中, 協議要放在發送方所在的協議中, 因爲發送方有可能是接收方的成員, 如果協議放在接收方中, 頭文件包含會形成一個環.
conformsToProtocol:@protocol()
15.文件
1)關於文件本身的操作 (NSFileManager 文件管理器)
[NSFileManager defaultManager] // 聲明一個NSFileManager對象
[contentsOfDirectoryAtPath: error: &error] // 淺度遍歷, 查看當前目錄下的內容, 返回值是數組; 如果沒有錯誤, error返回nil, 否則, 會在堆中創建個NSError的對象, 並將該對象地址賦給error; 傳地址, 就是爲了修改地址中存的值
[subpathsOfDirectoryAtPath: error: &error] // 深度遍歷, 不僅遍歷當前目錄的文件, 也遍歷子目錄下的內容
createDirectoryAtPath: withIntermediateDirectories: NO attributes: nil error: &error];
// 創建一個目錄; 第二個參數, 如果傳入YES, 會自動創建中間目錄(mkdir -p), 如果傳入NO, 只要中間目錄不存在, 就報錯; 第三個參數, 設置該目錄的屬性, 傳入nil, 爲一般(默認缺省)屬性;
createFileAtPath: contents: attributes: // 創建文件
字符串自帶一個dataUsingEncoding: 將一個字符串存入NSData中, data.bytes讀取data中的內容
removeItemAtPath: error:&error // 刪除文件或目錄
copyItemAtPath: toPath: error: // 拷貝, 文件名必須寫全
attributesOfItemAtPath: error: // 獲得文件屬性, 放在字典中
fileExistsAtPath: // 判斷文件是否存在
fileExistsAtPath: isDirectory: // 判斷文件是否存在, 並且是否是文件夾
2)關於文件內容的操作 (NSFileHandle 文件句柄)
從文件到內存是讀, 從內存到文件是寫
file pointer(指針)文件指針 file descriptor(數字)文件描述符 file handle(對象)文件句柄, 往文件句柄裏寫就是往文件中寫
[NSFileHandle fileHandleForReadingAtPath: ] // 以只讀的方式打開文件生成文件句柄
readDataToEndOfFile
readDataOfLength // read讀兩次的時候, 不是從頭再開始讀, 而是每次讀都接着上次讀到的位置往下讀
字符串的方法: initWithData: encoding // data轉字符串 dataUsingEncoding: // 字符串轉data
fileHandleForWritingAtPath: // 以只寫的方式打開文件, 如果文件不存在, 則創建文件, 在C中"w"會清空原文件, OC是一個一個的覆蓋
writeData: // 第一次從頭開始寫, 第二次接着往下寫
seekToEndOfFile // 將讀寫指針置到文件尾
seekToFileOffset: // 將讀寫指針置到文件指定位置, 0就是文件首
truncateFileAtOffset: // 清空(截斷)一個文件, 只剩下前面n個字節
fileHandleForUpdatingAtPath: // 讀寫操作
16.NSDate
NSDate * date = [NSDate date]; // 使用當前時間創建一個date對象
[NSDate dateWithTimeIntervalSinceNow: seconds] // 用一個時間間隔(seconds)來表示過去或未來的某一時間
[[NSDate date] timeIntervalSinceDate: date] // 將日期與保存在date中的日期進行比較
[NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: ]]; // 讓應用程序休眠一段時間
NSDateFormatter // 將日期轉化爲完全格式化的字符串
dateFormatter.dateFormat = @"MM/dd/YY HH:mm:ss";
[dateFormatter stringFromDate: [NSDate date]];
[NSTimer scheduledTimerWithTimeInterval: 1 target: self selector: @selector() userInfo: nil repeats: YES]
// 1秒後觸發此定時器, 並不斷循環下去, 直到定時器被禁用([timer invalidate])